From 6294b6ab217a2d5f1d2bc23a64505a228294c508 Mon Sep 17 00:00:00 2001 From: Pierre Pronchery Date: Wed, 22 Apr 2026 15:13:41 +0200 Subject: [PATCH] Vendor import of pkgconf 2.5.1 Obtained from https://github.com/pkgconf/pkgconf/archive/refs/tags/pkgconf-2.5.1.tar.gz SHA1: c5d6a0e62f293b2c9078c815b2343b0f208c9879 - SHA256: 79721badcad1987dead9c3609eb4877ab9b58821c06bdacb824f2c8897c11f2a - SHA512: 53244f372ea21125a1d97c5b89a84299740b55a66165782e807ed23adab3a07408a1547f1f40156e3060359660d07f49846c8b4893beef10ac9440ab7e8611cc - --- AUTHORS | 41 + CODE_OF_CONDUCT.md | 27 + COPYING | 10 + Kyuafile.in | 5 + Makefile.am | 217 ++ Makefile.lite | 77 + NEWS | 943 ++++++++ README.md | 130 ++ autogen.sh | 87 + cli/bomtool/main.c | 368 ++++ cli/getopt_long.c | 643 ++++++ cli/getopt_long.h | 70 + cli/main.c | 1823 ++++++++++++++++ cli/renderer-msvc.c | 172 ++ cli/renderer-msvc.h | 23 + configure.ac | 66 + doc/conf.py | 339 +++ doc/extract.py | 149 ++ doc/index.rst | 14 + doc/libpkgconf-argvsplit.rst | 23 + doc/libpkgconf-audit.rst | 35 + doc/libpkgconf-cache.rst | 45 + doc/libpkgconf-client.rst | 212 ++ doc/libpkgconf-dependency.rst | 90 + doc/libpkgconf-fragment.rst | 116 + doc/libpkgconf-path.rst | 71 + doc/libpkgconf-personality.rst | 27 + doc/libpkgconf-pkg.rst | 154 ++ doc/libpkgconf-queue.rst | 78 + doc/libpkgconf-tuple.rst | 92 + doc/libpkgconf.rst | 17 + libpkgconf.pc.in | 12 + libpkgconf/argvsplit.c | 161 ++ libpkgconf/audit.c | 98 + libpkgconf/bsdstubs.c | 197 ++ libpkgconf/bsdstubs.h | 36 + libpkgconf/buffer.c | 87 + libpkgconf/cache.c | 231 ++ libpkgconf/client.c | 817 +++++++ libpkgconf/config.h.meson | 79 + libpkgconf/dependency.c | 506 +++++ libpkgconf/fileio.c | 113 + libpkgconf/fragment.c | 803 +++++++ libpkgconf/iter.h | 113 + libpkgconf/libpkgconf-api.h | 19 + libpkgconf/libpkgconf.h | 493 +++++ libpkgconf/meson.build | 12 + libpkgconf/parser.c | 115 + libpkgconf/path.c | 459 ++++ libpkgconf/personality.c | 359 +++ libpkgconf/pkg.c | 1935 +++++++++++++++++ libpkgconf/queue.c | 408 ++++ libpkgconf/stdinc.h | 75 + libpkgconf/tuple.c | 476 ++++ libpkgconf/win-dirent.h | 1028 +++++++++ m4/ax_check_compile_flag.m4 | 53 + man/bomtool.1 | 100 + man/pc.5 | 178 ++ man/pkg.m4.7 | 143 ++ man/pkgconf-personality.5 | 100 + man/pkgconf.1 | 758 +++++++ meson.build | 199 ++ meson_options.txt | 19 + pkg.m4 | 350 +++ pkgconf.wxs.in | 64 + tests/Kyuafile.in | 14 + tests/basic.sh | 391 ++++ tests/builtins.sh | 68 + tests/conflicts.sh | 23 + tests/framework.sh | 20 + tests/lib-relocatable/lib/pkgconfig/foo.pc | 11 + tests/lib1/argv-parse-2.pc | 10 + tests/lib1/argv-parse-3.pc | 11 + tests/lib1/argv-parse.pc | 10 + tests/lib1/bar.pc | 10 + tests/lib1/baz.pc | 11 + tests/lib1/billion-laughs.pc | 13 + tests/lib1/c-comment.pc | 17 + tests/lib1/case-sensitivity.pc | 7 + tests/lib1/cflags-internal.pc | 9 + tests/lib1/cflags-libs-only.pc | 10 + tests/lib1/cflags-libs-private-a.pc | 7 + tests/lib1/cflags-libs-private-b.pc | 7 + tests/lib1/cflags-libs-private-c.pc | 6 + tests/lib1/cflags-whitespace-trailing.pc | 4 + tests/lib1/cflags-whitespace.pc | 4 + .../child-prefix/pkgconfig/child-prefix-1.pc | 11 + tests/lib1/circular-1.pc | 11 + tests/lib1/circular-2.pc | 11 + tests/lib1/circular-3.pc | 11 + tests/lib1/comments-in-fields.pc | 9 + tests/lib1/comments.pc | 6 + tests/lib1/conflicts.pc | 11 + tests/lib1/depgraph-break.pc | 12 + tests/lib1/dos-lineendings.pc | 10 + tests/lib1/empty-tuple.pc | 6 + tests/lib1/escaped-backslash.pc | 4 + tests/lib1/explicit-sysroot.pc | 7 + tests/lib1/flag-order-1.pc | 10 + tests/lib1/flag-order-3.pc | 10 + tests/lib1/foo.pc | 12 + tests/lib1/foobar.pc | 12 + tests/lib1/fragment-collision-1.pc | 4 + tests/lib1/fragment-collision-2.pc | 4 + tests/lib1/fragment-collision-intermediary.pc | 5 + tests/lib1/fragment-collision.pc | 6 + tests/lib1/fragment-comment.pc | 4 + tests/lib1/fragment-escaping-1.pc | 5 + tests/lib1/fragment-escaping-2.pc | 4 + tests/lib1/fragment-escaping-3.pc | 4 + tests/lib1/fragment-group-a.pc | 4 + tests/lib1/fragment-group-b.pc | 4 + tests/lib1/fragment-group-c.pc | 4 + tests/lib1/fragment-groups-2.pc | 4 + tests/lib1/fragment-groups.pc | 4 + tests/lib1/fragment-quoting-2.pc | 10 + tests/lib1/fragment-quoting-3.pc | 10 + tests/lib1/fragment-quoting-5.pc | 10 + tests/lib1/fragment-quoting-7.pc | 4 + tests/lib1/fragment-quoting.pc | 10 + tests/lib1/framework-1.pc | 9 + tests/lib1/framework-2.pc | 10 + tests/lib1/idirafter-ordering.pc | 4 + tests/lib1/idirafter.pc | 4 + tests/lib1/incomplete.pc | 4 + tests/lib1/intermediary-1.pc | 10 + tests/lib1/intermediary-2.pc | 10 + tests/lib1/isystem.pc | 4 + tests/lib1/malformed-1.pc | 2 + tests/lib1/malformed-quoting.pc | 4 + tests/lib1/malformed-version.pc | 5 + tests/lib1/metapackage-1.pc | 6 + tests/lib1/metapackage-2.pc | 5 + tests/lib1/metapackage-3.pc | 4 + tests/lib1/metapackage.pc | 4 + tests/lib1/missing-require.pc | 11 + tests/lib1/multiline-bogus.pc | 9 + tests/lib1/multiline.pc | 9 + tests/lib1/no-trailing-newline.pc | 10 + tests/lib1/nocflag.pc | 9 + tests/lib1/nolib.pc | 9 + tests/lib1/omg-sysroot-uninstalled.pc | 10 + tests/lib1/omg-uninstalled.pc | 10 + tests/lib1/orphaned-requires-private.pc | 8 + tests/lib1/paren-quoting.pc | 5 + tests/lib1/pcfiledir.pc | 8 + tests/lib1/prefix-foo1.pc | 12 + tests/lib1/prefix-foo2.pc | 12 + tests/lib1/private-libs-duplication.pc | 7 + tests/lib1/provides-request-simple.pc | 6 + tests/lib1/provides.pc | 6 + tests/lib1/quotes.pc | 10 + tests/lib1/requires-internal-2.pc | 9 + tests/lib1/requires-internal-collision.pc | 10 + tests/lib1/requires-internal-missing.pc | 10 + tests/lib1/requires-internal.pc | 10 + tests/lib1/spaces-in-paths.pc | 7 + tests/lib1/static-archive-libs.pc | 10 + tests/lib1/static-libs.pc | 12 + tests/lib1/sysroot-dir-2.pc | 7 + tests/lib1/sysroot-dir-3.pc | 9 + tests/lib1/sysroot-dir-4.pc | 9 + tests/lib1/sysroot-dir-5.pc | 9 + tests/lib1/sysroot-dir.pc | 5 + tests/lib1/tilde-quoting.pc | 5 + tests/lib1/tilde.pc | 11 + tests/lib1/truncated.pc | 1 + tests/lib1/tuple-quoting.pc | 7 + tests/lib1/typelibdir.pc | 11 + tests/lib1/unavailable-provider.pc | 11 + tests/lib1/utf8.pc | 10 + tests/lib1/variable-whitespace.pc | 11 + tests/lib2/foo.pc | 10 + tests/lib3/bar.pc | 10 + tests/meson.build | 23 + tests/parser.sh | 372 ++++ tests/provides.sh | 310 +++ tests/regress.sh | 354 +++ tests/requires.sh | 176 ++ tests/symlink.sh | 122 ++ tests/sysroot.sh | 110 + tests/test_env.sh.in | 50 + tests/version.sh | 38 + txt2rtf.py | 35 + 184 files changed, 19696 insertions(+) create mode 100644 AUTHORS create mode 100644 CODE_OF_CONDUCT.md create mode 100644 COPYING create mode 100644 Kyuafile.in create mode 100644 Makefile.am create mode 100644 Makefile.lite create mode 100644 NEWS create mode 100644 README.md create mode 100755 autogen.sh create mode 100644 cli/bomtool/main.c create mode 100644 cli/getopt_long.c create mode 100644 cli/getopt_long.h create mode 100644 cli/main.c create mode 100644 cli/renderer-msvc.c create mode 100644 cli/renderer-msvc.h create mode 100644 configure.ac create mode 100644 doc/conf.py create mode 100644 doc/extract.py create mode 100644 doc/index.rst create mode 100644 doc/libpkgconf-argvsplit.rst create mode 100644 doc/libpkgconf-audit.rst create mode 100644 doc/libpkgconf-cache.rst create mode 100644 doc/libpkgconf-client.rst create mode 100644 doc/libpkgconf-dependency.rst create mode 100644 doc/libpkgconf-fragment.rst create mode 100644 doc/libpkgconf-path.rst create mode 100644 doc/libpkgconf-personality.rst create mode 100644 doc/libpkgconf-pkg.rst create mode 100644 doc/libpkgconf-queue.rst create mode 100644 doc/libpkgconf-tuple.rst create mode 100644 doc/libpkgconf.rst create mode 100644 libpkgconf.pc.in create mode 100644 libpkgconf/argvsplit.c create mode 100644 libpkgconf/audit.c create mode 100644 libpkgconf/bsdstubs.c create mode 100644 libpkgconf/bsdstubs.h create mode 100644 libpkgconf/buffer.c create mode 100644 libpkgconf/cache.c create mode 100644 libpkgconf/client.c create mode 100644 libpkgconf/config.h.meson create mode 100644 libpkgconf/dependency.c create mode 100644 libpkgconf/fileio.c create mode 100644 libpkgconf/fragment.c create mode 100644 libpkgconf/iter.h create mode 100644 libpkgconf/libpkgconf-api.h create mode 100644 libpkgconf/libpkgconf.h create mode 100644 libpkgconf/meson.build create mode 100644 libpkgconf/parser.c create mode 100644 libpkgconf/path.c create mode 100644 libpkgconf/personality.c create mode 100644 libpkgconf/pkg.c create mode 100644 libpkgconf/queue.c create mode 100644 libpkgconf/stdinc.h create mode 100644 libpkgconf/tuple.c create mode 100644 libpkgconf/win-dirent.h create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 man/bomtool.1 create mode 100644 man/pc.5 create mode 100644 man/pkg.m4.7 create mode 100644 man/pkgconf-personality.5 create mode 100644 man/pkgconf.1 create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 pkg.m4 create mode 100644 pkgconf.wxs.in create mode 100644 tests/Kyuafile.in create mode 100755 tests/basic.sh create mode 100755 tests/builtins.sh create mode 100755 tests/conflicts.sh create mode 100755 tests/framework.sh create mode 100644 tests/lib-relocatable/lib/pkgconfig/foo.pc create mode 100644 tests/lib1/argv-parse-2.pc create mode 100644 tests/lib1/argv-parse-3.pc create mode 100644 tests/lib1/argv-parse.pc create mode 100644 tests/lib1/bar.pc create mode 100644 tests/lib1/baz.pc create mode 100644 tests/lib1/billion-laughs.pc create mode 100644 tests/lib1/c-comment.pc create mode 100644 tests/lib1/case-sensitivity.pc create mode 100644 tests/lib1/cflags-internal.pc create mode 100644 tests/lib1/cflags-libs-only.pc create mode 100644 tests/lib1/cflags-libs-private-a.pc create mode 100644 tests/lib1/cflags-libs-private-b.pc create mode 100644 tests/lib1/cflags-libs-private-c.pc create mode 100644 tests/lib1/cflags-whitespace-trailing.pc create mode 100644 tests/lib1/cflags-whitespace.pc create mode 100644 tests/lib1/child-prefix/pkgconfig/child-prefix-1.pc create mode 100644 tests/lib1/circular-1.pc create mode 100644 tests/lib1/circular-2.pc create mode 100644 tests/lib1/circular-3.pc create mode 100644 tests/lib1/comments-in-fields.pc create mode 100644 tests/lib1/comments.pc create mode 100644 tests/lib1/conflicts.pc create mode 100644 tests/lib1/depgraph-break.pc create mode 100644 tests/lib1/dos-lineendings.pc create mode 100644 tests/lib1/empty-tuple.pc create mode 100644 tests/lib1/escaped-backslash.pc create mode 100644 tests/lib1/explicit-sysroot.pc create mode 100644 tests/lib1/flag-order-1.pc create mode 100644 tests/lib1/flag-order-3.pc create mode 100644 tests/lib1/foo.pc create mode 100644 tests/lib1/foobar.pc create mode 100644 tests/lib1/fragment-collision-1.pc create mode 100644 tests/lib1/fragment-collision-2.pc create mode 100644 tests/lib1/fragment-collision-intermediary.pc create mode 100644 tests/lib1/fragment-collision.pc create mode 100644 tests/lib1/fragment-comment.pc create mode 100644 tests/lib1/fragment-escaping-1.pc create mode 100644 tests/lib1/fragment-escaping-2.pc create mode 100644 tests/lib1/fragment-escaping-3.pc create mode 100644 tests/lib1/fragment-group-a.pc create mode 100644 tests/lib1/fragment-group-b.pc create mode 100644 tests/lib1/fragment-group-c.pc create mode 100644 tests/lib1/fragment-groups-2.pc create mode 100644 tests/lib1/fragment-groups.pc create mode 100644 tests/lib1/fragment-quoting-2.pc create mode 100644 tests/lib1/fragment-quoting-3.pc create mode 100644 tests/lib1/fragment-quoting-5.pc create mode 100644 tests/lib1/fragment-quoting-7.pc create mode 100644 tests/lib1/fragment-quoting.pc create mode 100644 tests/lib1/framework-1.pc create mode 100644 tests/lib1/framework-2.pc create mode 100644 tests/lib1/idirafter-ordering.pc create mode 100644 tests/lib1/idirafter.pc create mode 100644 tests/lib1/incomplete.pc create mode 100644 tests/lib1/intermediary-1.pc create mode 100644 tests/lib1/intermediary-2.pc create mode 100644 tests/lib1/isystem.pc create mode 100644 tests/lib1/malformed-1.pc create mode 100644 tests/lib1/malformed-quoting.pc create mode 100644 tests/lib1/malformed-version.pc create mode 100644 tests/lib1/metapackage-1.pc create mode 100644 tests/lib1/metapackage-2.pc create mode 100644 tests/lib1/metapackage-3.pc create mode 100644 tests/lib1/metapackage.pc create mode 100644 tests/lib1/missing-require.pc create mode 100644 tests/lib1/multiline-bogus.pc create mode 100644 tests/lib1/multiline.pc create mode 100644 tests/lib1/no-trailing-newline.pc create mode 100644 tests/lib1/nocflag.pc create mode 100644 tests/lib1/nolib.pc create mode 100644 tests/lib1/omg-sysroot-uninstalled.pc create mode 100644 tests/lib1/omg-uninstalled.pc create mode 100644 tests/lib1/orphaned-requires-private.pc create mode 100644 tests/lib1/paren-quoting.pc create mode 100644 tests/lib1/pcfiledir.pc create mode 100644 tests/lib1/prefix-foo1.pc create mode 100644 tests/lib1/prefix-foo2.pc create mode 100644 tests/lib1/private-libs-duplication.pc create mode 100644 tests/lib1/provides-request-simple.pc create mode 100644 tests/lib1/provides.pc create mode 100644 tests/lib1/quotes.pc create mode 100644 tests/lib1/requires-internal-2.pc create mode 100644 tests/lib1/requires-internal-collision.pc create mode 100644 tests/lib1/requires-internal-missing.pc create mode 100644 tests/lib1/requires-internal.pc create mode 100644 tests/lib1/spaces-in-paths.pc create mode 100644 tests/lib1/static-archive-libs.pc create mode 100644 tests/lib1/static-libs.pc create mode 100644 tests/lib1/sysroot-dir-2.pc create mode 100644 tests/lib1/sysroot-dir-3.pc create mode 100644 tests/lib1/sysroot-dir-4.pc create mode 100644 tests/lib1/sysroot-dir-5.pc create mode 100644 tests/lib1/sysroot-dir.pc create mode 100644 tests/lib1/tilde-quoting.pc create mode 100644 tests/lib1/tilde.pc create mode 100644 tests/lib1/truncated.pc create mode 100644 tests/lib1/tuple-quoting.pc create mode 100644 tests/lib1/typelibdir.pc create mode 100644 tests/lib1/unavailable-provider.pc create mode 100644 tests/lib1/utf8.pc create mode 100644 tests/lib1/variable-whitespace.pc create mode 100644 tests/lib2/foo.pc create mode 100644 tests/lib3/bar.pc create mode 100644 tests/meson.build create mode 100755 tests/parser.sh create mode 100755 tests/provides.sh create mode 100755 tests/regress.sh create mode 100755 tests/requires.sh create mode 100755 tests/symlink.sh create mode 100755 tests/sysroot.sh create mode 100644 tests/test_env.sh.in create mode 100755 tests/version.sh create mode 100644 txt2rtf.py diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000000..9ba1de45cb0 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,41 @@ +A. Wilcox +Alexander Tsoy +Alexpux +Alon Bar-Lev +Alyx +Ariadne Conill +Baptiste Daroussin +Baptiste Daroussin +Bryan Drewery +Dag-Erling Smørgrav +Dan Kegel +Dan Kegel +Dan Nicholson +David Michael +Emil Renner Berthing +Fabian Groffen +Graham Ollis +Gregor Richards +Ignacio Casal Quinteiro +Igor Gnatenko +Issam Maghni +JD Horelick +Jason Dusek +Javier Viguera +Jean-Sébastien Pédron +John Hein +Jussi Pakkanen +Leorize +Luca Barbato +Marcin Wojdyr +Maxin B. John +Michał Górny +Mike Frysinger +Seungha Yang +TingPing +Tobias Kortkamp +Tony Theodore +Volker Braun +Yu Kobayashi +orbea +✈ Graham ✈ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..1dc77b3ef9a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,27 @@ +# pkgconf Code of Conduct + +We invite and encourage everybody to express their opinions on relevant +topics. All participants should at all times feel at ease to do so without +fearing any form of attack, reprisal or harassment. We ask everybody to be +respectful and considerate towards each other, especially when attempting +to provide constructive criticism. + +To foster tolerance, respect and hospitality in our community, we agree not +to engage in discriminatory, disparaging or offensive speech or actions, +including as to (but not limited to) gender, sexuality, race, nationality, +religion or profession. We are a community of many different nationalities +and backgrounds, and we cherish our strength in diversity. + + +## Reporting incidents + +Please report any incidents which may be perceived as violations to +`ariadne+conduct@dereferenced.org`. Incidents will be investigated and, +if warranted, acted upon. + + +## Credits + +This CoC is derived from the [FSFE Code of Conduct][fsfe-coc]. + + [fsfe-coc]: https://fsfe.org/about/codeofconduct.en.html diff --git a/COPYING b/COPYING new file mode 100644 index 00000000000..35c7c16aa7e --- /dev/null +++ b/COPYING @@ -0,0 +1,10 @@ +Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + pkgconf authors (see AUTHORS file in source directory). + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +This software is provided 'as is' and without any warranty, express or +implied. In no event shall the authors be liable for any damages arising +from the use of this software. diff --git a/Kyuafile.in b/Kyuafile.in new file mode 100644 index 00000000000..1490fef49c8 --- /dev/null +++ b/Kyuafile.in @@ -0,0 +1,5 @@ +syntax(2) + +test_suite('pkgconf') + +include('tests/Kyuafile') diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000000..93486a17a0b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,217 @@ +libdir = @libdir@ +datarootdir = @datarootdir@ +datadir = @datadir@ +includedir = @includedir@ +system_includedir = @SYSTEM_INCLUDEDIR@ +system_libdir = @SYSTEM_LIBDIR@ +pkg_default_dir = @PKG_DEFAULT_PATH@ +personality_dir = @PERSONALITY_PATH@ +pkgconfigdir = $(libdir)/pkgconfig +nodist_pkgconfig_DATA = libpkgconf.pc + +ACLOCAL_AMFLAGS = -I m4 +AM_CFLAGS = -DPERSONALITY_PATH=\"$(personality_dir)\" -DPKG_DEFAULT_PATH=\"$(pkg_default_dir)\" -DSYSTEM_INCLUDEDIR=\"$(system_includedir)\" -DSYSTEM_LIBDIR=\"$(system_libdir)\" + +bin_PROGRAMS = pkgconf bomtool +lib_LTLIBRARIES = libpkgconf.la + +EXTRA_DIST = pkg.m4 \ + meson.build \ + meson_options.txt \ + pkgconf.wxs.in \ + txt2rtf.py \ + libpkgconf/meson.build \ + libpkgconf/config.h.meson \ + libpkgconf/win-dirent.h \ + tests/lib-relocatable/lib/pkgconfig/foo.pc \ + tests/lib1/argv-parse-2.pc \ + tests/lib1/billion-laughs.pc \ + tests/lib1/dos-lineendings.pc \ + tests/lib1/paren-quoting.pc \ + tests/lib1/argv-parse-3.pc \ + tests/lib1/foo.pc \ + tests/lib1/foobar.pc \ + tests/lib1/unavailable-provider.pc \ + tests/lib1/prefix-foo1.pc \ + tests/lib1/argv-parse.pc \ + tests/lib1/framework-1.pc \ + tests/lib1/prefix-foo2.pc \ + tests/lib1/bar.pc \ + tests/lib1/framework-2.pc \ + tests/lib1/private-libs-duplication.pc \ + tests/lib1/baz.pc \ + tests/lib1/incomplete.pc \ + tests/lib1/quotes.pc \ + tests/lib1/case-sensitivity.pc \ + tests/lib1/intermediary-1.pc \ + tests/lib1/static-archive-libs.pc \ + tests/lib1/cflags-libs-only.pc \ + tests/lib1/intermediary-2.pc \ + tests/lib1/static-libs.pc \ + tests/lib1/circular-1.pc \ + tests/lib1/missing-require.pc \ + tests/lib1/sysroot-dir.pc \ + tests/lib1/circular-2.pc \ + tests/lib1/multiline.pc \ + tests/lib1/multiline-bogus.pc \ + tests/lib1/tilde-quoting.pc \ + tests/lib1/circular-3.pc \ + tests/lib1/no-trailing-newline.pc \ + tests/lib1/tilde.pc \ + tests/lib1/comments-in-fields.pc \ + tests/lib1/nocflag.pc \ + tests/lib1/typelibdir.pc \ + tests/lib2/foo.pc \ + tests/lib1/comments.pc \ + tests/lib1/nolib.pc \ + tests/lib3/bar.pc \ + tests/lib1/conflicts.pc \ + tests/lib1/omg-uninstalled.pc \ + tests/lib1/omg-sysroot-uninstalled.pc \ + tests/lib1/isystem.pc \ + tests/lib1/idirafter.pc \ + tests/lib1/idirafter-ordering.pc \ + tests/lib1/depgraph-break.pc \ + tests/lib1/cflags-whitespace.pc \ + tests/lib1/cflags-whitespace-trailing.pc \ + tests/lib1/provides.pc \ + tests/lib1/provides-request-simple.pc \ + tests/lib1/flag-order-1.pc \ + tests/lib1/flag-order-3.pc \ + tests/lib1/variable-whitespace.pc \ + tests/lib1/fragment-collision.pc \ + tests/lib1/fragment-collision-intermediary.pc \ + tests/lib1/fragment-collision-1.pc \ + tests/lib1/fragment-collision-2.pc \ + tests/lib1/fragment-comment.pc \ + tests/lib1/fragment-escaping-1.pc \ + tests/lib1/fragment-escaping-2.pc \ + tests/lib1/fragment-escaping-3.pc \ + tests/lib1/fragment-quoting.pc \ + tests/lib1/fragment-quoting-2.pc \ + tests/lib1/fragment-quoting-3.pc \ + tests/lib1/fragment-quoting-5.pc \ + tests/lib1/fragment-quoting-7.pc \ + tests/lib1/fragment-groups.pc \ + tests/lib1/fragment-groups-2.pc \ + tests/lib1/fragment-group-a.pc \ + tests/lib1/fragment-group-b.pc \ + tests/lib1/fragment-group-c.pc \ + tests/lib1/malformed-1.pc \ + tests/lib1/malformed-quoting.pc \ + tests/lib1/malformed-version.pc \ + tests/lib1/metapackage.pc \ + tests/lib1/metapackage-1.pc \ + tests/lib1/metapackage-2.pc \ + tests/lib1/metapackage-3.pc \ + tests/lib1/explicit-sysroot.pc \ + tests/lib1/escaped-backslash.pc \ + tests/lib1/cflags-internal.pc \ + tests/lib1/requires-internal.pc \ + tests/lib1/requires-internal-2.pc \ + tests/lib1/requires-internal-missing.pc \ + tests/lib1/requires-internal-collision.pc \ + tests/lib1/tuple-quoting.pc \ + tests/lib1/empty-tuple.pc \ + tests/lib1/orphaned-requires-private.pc \ + tests/lib1/pcfiledir.pc \ + tests/lib1/sysroot-dir-2.pc \ + tests/lib1/sysroot-dir-3.pc \ + tests/lib1/sysroot-dir-4.pc \ + tests/lib1/sysroot-dir-5.pc \ + tests/lib1/child-prefix/pkgconfig/child-prefix-1.pc \ + tests/lib1/cflags-libs-private-a.pc \ + tests/lib1/cflags-libs-private-b.pc \ + tests/lib1/cflags-libs-private-c.pc \ + tests/lib1/truncated.pc \ + tests/lib1/c-comment.pc \ + tests/meson.build \ + $(test_scripts) \ + doc/conf.py \ + doc/extract.py \ + doc/index.rst \ + doc/libpkgconf.rst \ + doc/libpkgconf-argvsplit.rst \ + doc/libpkgconf-audit.rst \ + doc/libpkgconf-cache.rst \ + doc/libpkgconf-client.rst \ + doc/libpkgconf-dependency.rst \ + doc/libpkgconf-fragment.rst \ + doc/libpkgconf-path.rst \ + doc/libpkgconf-pkg.rst \ + doc/libpkgconf-queue.rst \ + doc/libpkgconf-tuple.rst + +test_scripts= tests/basic.sh \ + tests/builtins.sh \ + tests/conflicts.sh \ + tests/framework.sh \ + tests/parser.sh \ + tests/provides.sh \ + tests/regress.sh \ + tests/requires.sh \ + tests/symlink.sh \ + tests/sysroot.sh \ + tests/version.sh + +test_sh = $(test_scripts) +check_SCRIPTS = ${test_sh:.sh=} + +SUFFIXES= .sh + +nobase_pkginclude_HEADERS = libpkgconf/bsdstubs.h libpkgconf/iter.h libpkgconf/libpkgconf.h libpkgconf/stdinc.h libpkgconf/libpkgconf-api.h +libpkgconf_la_SOURCES = \ + libpkgconf/audit.c \ + libpkgconf/buffer.c \ + libpkgconf/cache.c \ + libpkgconf/client.c \ + libpkgconf/pkg.c \ + libpkgconf/bsdstubs.c \ + libpkgconf/fragment.c \ + libpkgconf/argvsplit.c \ + libpkgconf/fileio.c \ + libpkgconf/tuple.c \ + libpkgconf/dependency.c \ + libpkgconf/queue.c \ + libpkgconf/path.c \ + libpkgconf/personality.c \ + libpkgconf/parser.c +libpkgconf_la_LDFLAGS = -no-undefined -version-info 7:0:0 -export-symbols-regex '^pkgconf_' + +dist_man_MANS = \ + man/bomtool.1 \ + man/pkgconf.1 \ + man/pkg.m4.7 \ + man/pc.5 \ + man/pkgconf-personality.5 + +pkgconf_LDADD = libpkgconf.la +pkgconf_SOURCES = \ + cli/main.c \ + cli/getopt_long.c \ + cli/renderer-msvc.c +pkgconf_CPPFLAGS = -I$(top_srcdir)/libpkgconf -I$(top_srcdir)/cli +noinst_HEADERS = \ + cli/getopt_long.h \ + cli/renderer-msvc.h + +bomtool_LDADD = libpkgconf.la +bomtool_SOURCES = \ + cli/bomtool/main.c \ + cli/getopt_long.c +bomtool_CPPFLAGS = -I$(top_srcdir)/libpkgconf -I$(top_srcdir)/cli -I$(top_srcdir)/cli/bomtool + +dist_doc_DATA = README.md AUTHORS + +m4datadir = $(datadir)/aclocal +m4data_DATA = pkg.m4 + +CLEANFILES = $(EXTRA_PROGRAMS) \ + $(check_SCRIPTS) + +check: pkgconf $(check_SCRIPTS) + kyua --config=none test --kyuafile='$(top_builddir)/Kyuafile' \ + --build-root='$(top_builddir)' + +.sh: + install -m 755 $< $@ diff --git a/Makefile.lite b/Makefile.lite new file mode 100644 index 00000000000..6d123433119 --- /dev/null +++ b/Makefile.lite @@ -0,0 +1,77 @@ +# Copyright (c) 2019 William Pitcock +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# This software is provided 'as is' and without any warranty, express or +# implied. In no event shall the authors be liable for any damages arising +# from the use of this software. + +# pkgconf-lite is a staticly-linked version of pkgconf that does not include +# all features, notably it does not include cross-compile support and MSVC +# support. It does not include the libpkgconf library. + +SRCS = \ + libpkgconf/argvsplit.c \ + libpkgconf/audit.c \ + libpkgconf/bsdstubs.c \ + libpkgconf/cache.c \ + libpkgconf/client.c \ + libpkgconf/dependency.c \ + libpkgconf/fileio.c \ + libpkgconf/fragment.c \ + libpkgconf/parser.c \ + libpkgconf/path.c \ + libpkgconf/personality.c \ + libpkgconf/pkg.c \ + libpkgconf/queue.c \ + libpkgconf/tuple.c \ + cli/getopt_long.c \ + cli/main.c +OBJS = ${SRCS:.c=.o} +CFLAGS = ${STATIC} -DPKGCONF_LITE -I. -Ilibpkgconf -Icli -DSYSTEM_LIBDIR=\"${SYSTEM_LIBDIR}\" -DSYSTEM_INCLUDEDIR=\"${SYSTEM_INCLUDEDIR}\" -DPKG_DEFAULT_PATH=\"${PKG_DEFAULT_PATH}\" +STATIC = +STRIP = strip + +all: pkgconf-lite + +libpkgconf/config.h: + @echo '#define PACKAGE_NAME "pkgconf-lite"' >> $@ + @echo '#define PACKAGE_BUGREPORT "https://git.dereferenced.org/pkgconf/pkgconf/issues"' >> $@ + @echo '#define PACKAGE_VERSION "2.5.1"' >> $@ + @echo '#define PACKAGE PACKAGE_NAME " " PACKAGE_VERSION' >> $@ + @echo '#define HAVE_STRLCPY' >> $@ + @echo '#define HAVE_STRLCAT' >> $@ + @echo '#define HAVE_STRNDUP' >> $@ + +pkgconf-lite: preflight libpkgconf/config.h ${OBJS} + ${CC} ${STATIC} -o $@ ${OBJS} + ${STRIP} $@ + +clean: + rm -f libpkgconf/config.h + rm -f ${OBJS} + rm -f pkgconf-lite + +preflight: preflight-system-libdir preflight-system-includedir preflight-pkg-default-path + +preflight-system-libdir: + @if test -z "${SYSTEM_LIBDIR}"; then \ + echo "SYSTEM_LIBDIR not set."; \ + exit 1; \ + fi + +preflight-system-includedir: + @if test -z "${SYSTEM_INCLUDEDIR}"; then \ + echo "SYSTEM_INCLUDEDIR not set."; \ + exit 1; \ + fi + +preflight-pkg-default-path: + @if test -z "${PKG_DEFAULT_PATH}"; then \ + echo "PKG_DEFAULT_PATH not set."; \ + exit 1; \ + fi + +.PHONY: preflight preflight-system-libdir preflight-system-includedir preflight-pkg-default-path clean diff --git a/NEWS b/NEWS new file mode 100644 index 00000000000..8b2947649e1 --- /dev/null +++ b/NEWS @@ -0,0 +1,943 @@ +Changes from previous version of pkgconf +======================================== + +Changes from 2.5.0 to 2.5.1: +---------------------------- + +* Fix processing of empty dependency lists. + +Changes from 2.4.3 to 2.5.0: +---------------------------- + +* Added a manual page for bomtool. + +* Add support for preloaded packages. + These are modules which are preloaded into the package database + and preferred over searching the module search path when present. + +* Refactor Windows registry PKG_CONFIG_PATH support so that it + augments the main directory search list instead of being treated + as a special case. + +* Processing of `--with-path` arguments by the pkgconf CLI is + now deferred until libpkgconf is fully initialized, effectively + aligning behavior with PKG_CONFIG_PATH processing. + +* Fix several minor memory safety bugs which were identified by + the GCC 15 static analyzer. + +* Added support for pledge(2) and unveil(2) on systems where + this functionality is available. + +* Significant improvements to pkgconf's manual pages. + Patches by Ingo Schwarze and Jonathan Gray (OpenBSD). + +* Remove questionable default-static assumption on Windows that + was inherited from the original pkg-config. Most distributions + of pkgconf on Windows were already patching this out. + Patch by Kai Pastor. + +* Add -D_POSIX_C_SOURCE=200809L to the build definitions, which + is needed for readlinkat on glibc. + Patch by Filipe Laíns. + +Changes from 2.4.2 to 2.4.3: +---------------------------- + +* Fix additional logic errors relating to the new fragment trees + functionality. + +Changes from 2.4.1 to 2.4.2: +---------------------------- + +* Fix several logic errors in the pkg-config file parser that were + surfaced by recent refactoring work. + +* Fix BSD make compatibility so that it generates the test data + before running kyua on BSD make implementations. + +Changes from 2.4.0 to 2.4.1: +---------------------------- + +* Ensure the full DAG is solved for all query types. + +Changes from 2.3.0 to 2.4.0: +---------------------------- + +* Allow multiple package names in solution-based queries such as + `--print-requires`, `--print-requires-private` and `--print-provides`. + +* Use `_DEFAULT_SOURCE` where appropriate on Meson. + +* Add an abstract buffer type and use it when loading files from disk + instead of a 64KB buffer. This ensures large pkg-config files are + not truncated. + +* Disable graph recursion in `--variable` queries as it was generating + duplicate output. + +* Add infrastructure for tracking fragment group relations and convert + storage of fragments to use a tree-like structure instead of string + concatenation. + +* Add support for tracking linker groups, e.g. + + -Wl,--start-group -la -lb -lc -Wl,--end-group + + as fragment groups. + +* Properly contextualize the sysroot directory when processing package + information, ensuring packages where ${pc_sysrootdir} does not match + the default are properly processed. + +Changes from 2.2.0 to 2.3.0: +---------------------------- + +* Fix compile with Meson on Solaris by defining __EXTENSIONS__. + +* Add support for the PKG_CONFIG_RELOCATE_PATHS environmental variable. + When set, the program will act as if --define-prefix is always enabled. + +* Color solution nodes that were part of the original query, and use + that coloring to skip over dependencies when generating DocumentNames + in bomtool. + +* Enhance --env option to support variables with both --variable=varname + and --print-variables. + +* Add --exists-cflags option which creates synthetic preprocessor + definition flags for every queried dependency when found. + +* Document that Requires.private is always used for header paths. + Patch by Petr Písař. + +* Fix minor documentation typos. + Patch by Pierce. + +* Ensure string comparisons using functions are done with + unsigned bytes to avoid undefined behavior. + Patch by Taylor R Campbell. + +* Fix parsing edge-case bugs with dependency versions. + Patch by Kai Pastor. + +* Change PKG_PROG_PKG_CONFIG autoconf macro to add a customizable + failure handler if pkg-config is not found. + Patch by Ismael Luceno. + +Changes from 2.1.1 to 2.2.0: +---------------------------- + +* libpkgconf SOVERSION is now 5. + +* Significant solver rework to flatten both requires and requires.private + dependencies in a single pass. Improves performance slightly and ensures + proper dependency order. + Patches by Kai Pastor. + +* Improve `--digraph` output to reflect more of the solver's state in the + rendered dependency graph. + Patches by Kai Pastor. + +* Do not reference the graph root by name when presenting error messages about + directly requested dependency nodes. + Patch by Kai Pastor. + +Changes from 2.1.0 to 2.1.1: +---------------------------- + +* Documentation fixes from Sam James and Stefan Weil. + +* Fix --modversion with constraints. + Patch by Kai Pastor. + +* Reintroduce an optimization to the dependency graph walker which avoids + revisiting already visited nodes. + Patch by Yi Chou with some modifications. + +* Add a regression test to check that the dependency flattener is working + as expected. + Patch by Kai Pastor. + +Changes from 2.0.3 to 2.1.0: +---------------------------- + +* Do not flatten the solver solution into the original world used as + input to the solver. + Patches by Kai Pastor. + +* Fix warnings with GCC 14 -Walloc-size. + Patch by Sam James. + +* Add --solution to the pkgconf CLI to dump the solver state. + +* Improve the --digraph output to clarify cancelled edges in a given + solution. + +* Demote requires dependencies to requires.private when a parent + dependency is pulled in via requires.private. + +* Trim trailing whitespace when processing package arguments. + Patch by Colin Gillespie. + +* Avoid strncmp() in --modversion version comparison. + Patch by Colin Gillespie. + +* Update autoconf compile flag checking macro. + Patch by Peter Kokot. + +* Add system default path configuration to Meson. + Patch by L. E. Segovia. + +* Fix order of PKG_CONFIG_LIBDIR and PKG_CONFIG_PATH element processing. + +Changes from 2.0.2 to 2.0.3: +---------------------------- + +* Fix some edge-cases with the new `--modversion` implementation + and add additional regression tests. + Patch by Colin Gillespie. + +* Fix some format specifiers to use PRIu64 in debug tracing. + +Changes from 2.0.1 to 2.0.2: +---------------------------- + +* Fix long-standing bug where package identifiers for "uninstalled" + packages incorrectly included the "-uninstalled" suffix. + + This was exposed by the recent change to `--modversion` in 2.0.1. + +Changes from 2.0.0 to 2.0.1: +---------------------------- + +* The behavior of --modversion was largely reverted back to the traditional + pkg-config behavior, but still operates on a solved dependency graph. + + The order of --modversion output is based on the dependency resolution + queue which is passed to the solver, which itself generally maps to the + order of the constrants provided on the command line. + +* A new flag, --verbose, has been added. When used with `--modversion`, it + is possible to disambiguate which version belongs to which module: + + % pkgconf --modversion --verbose foo bar + foo: 1.2.3 + bar: 1.3 + +Changes from 1.9.5 to 2.0.0: +---------------------------- + +* When flattening the dependency graph, retain the latest seen edges + rather than the earliest. + +* Fix a long-standing bug where the dependency resolution queue was + evaluated in reverse. This bug masked the aforementioned dependency + flattening bug in many cases. + +* Fix handling of --with-path, which was appending paths to the search + list rather than prepending them as intended. + +* Error when --modversion is requested with more than one package, as + the output is ambiguous. + +Changes from 1.9.4 to 1.9.5: +---------------------------- + +* Fix incorrect assumptions involving the use of ctype(3) functions. + Patch by Taylor R Campbell. + +* Fix detection of provided functions on autoconf. + Patches by Harmen Stoppels. + +* Fix deletion of tests/meson.build by the autoconf build system. + Patch by h30032433. + +* Fix quoting rules in argvsplit.c. + Patch by huyubiao. + +* Update libpkgconf documentation and documentation building scripts. + Patches by Andrew Shadura. + +* Enforce maximum package count correctly for --modversion. + +Changes from 1.9.3 to 1.9.4: +---------------------------- + +* Fix a buffer overflow vulnerability involving very large variable expansions. + CVE-2023-24056 + +* Fix a bunch of minor regressions with the solver. + +* Create separate solutions for `--cflags` and `--libs` when `--static` is not + used. + +* Remove final trailing whitespace in pkgconf_fragment_render_buf(). + +* Revert broken pkg.m4 change involving querying module versions in + PKG_CHECK_MODULES. + +* Fix handling of tildes in version strings. + +* Various C99 formatting string fixes involving SIZE_FMT_SPECIFIER. + +Changes from 1.9.2 to 1.9.3: +---------------------------- + +* Fix a bunch of minor code issues pointed out using Clang static analyzer. + +* New API: pkgconf_solution_free(), which frees a compiled solution graph. + +* Fix behavior when overriding global variables with `--define-variable`. + +Changes from 1.9.1 to 1.9.2: +---------------------------- + +* Do not try to break dependency cycles across dependency lists. This causes + the solved graph to sometimes miss required dependency nodes because the + solver detected an incorrect dependency cycle. + +* New API: pkgconf_queue_solve(), which replaces pkgconf_queue_apply(). + pkgconf_queue_apply is now deprecated and should not be used in new code. + +Changes from 1.9.0 to 1.9.1: +---------------------------- + +* Skip graph flattening and traversal for query types which only make sense + for a single pkg-config module. + + The old solver walked these graphs with --maximum-traverse-depth=1 in + these cases, but this is no longer helpful because the graph is flattened + by the new solver. + +Changes from 1.8.0 to 1.9.0: +---------------------------- + +* pkgconf 1.9.0 is the first testing release in the pkgconf 2.0 development + series. While it is believed to be suitable for production, there may be + bugs due to the overall redesign of the solver and other initiatives. + Additionally, a future release of pkgconf plans will have additional ABI + breaks for the libpkgconf library before the pkgconf 2.0 release is cut. + +* There is now a new solver that is designed to provide higher performance + with complicated graphs, which works by flattening the dependency graph + into a smaller set of dependencies. This graph can then be evaluated + instead of the original dependency graph without having to visit every + edge in the graph. + + NOTE: This solver, while providing significant performance improvements, + does so, at the cost of changed behavior for some edge cases (such as + circular dependencies). + +* Bug fixes: + - Resolved several memory leaks with edge cases when using libpkgconf + directly. + - pkgconf CLI now consistently frees libpkgconf resources under all + circumstances. + - SYSROOT rules are no longer applied to `-uninstalled` packages by + default. Use `PKG_CONFIG_PKGCONF1_SYSROOT_RULES` for legacy behavior. + +* A new `--license` selector has been added to the pkgconf CLI. This uses + SPDX expressions which can be set as the `License` field in `.pc` files. + See the `pc(5)` manpage for more information. + +* The canonical location for pkgconf maintenance going forward is + . This is presently + mirrored to GitHub for user convenience, but that mirroring will + be terminated at some point (due to GitHub Copilot). + +Changes from 1.7.4 to 1.8.0: +---------------------------- + +* This is the last planned maintenance branch. I see pkgconf as basically + a finished tool at this point, and very few people were ultimately interested + in libpkgconf. So, from here on out, it will just be bug fixes only and + very minor enhancements. + +* Bug fixes: + - Improved path handling on Windows to conform to what the MSYS2 + and Cygwin teams were already modifying pkgconf to do. + Patches by Christoph Reiter. + - Fix a minor memory leak relating to cross-personalities. + Patch by Stone Tickle. + - Fix static builds for Windows on Meson. + Patch by Alexander Neumann. + - Fix some edge cases with --redefine-prefix. + Patch by midipix. + - Do not prepend sysroot_dir if the .pc file does not exist in the + sysroot. + Patch by Sandro Mani. + - Do not perform path filtering on default system include and library + path lists. This fixes consistency with other mechanisms that modify + these path lists. + +* Enhancements: + - Document the --validate option in the manpage. + Patch by orbea. + +Changes from 1.7.3 to 1.7.4: +---------------------------- + +* Bug fixes: + - Fix null-dereference crash when pulling a malformed 'uninstalled' + .pc file into a dependency tree. Patch by Tobias Stöckmann. + - Fix truncation of comment characters when quoted. + - Fix handling of .pc module names in --list-all on Windows. + Patch by Ryan Scott. + - Handle platforms where realpath(3) requires a pre-allocated buffer. + Patch by Fabian Groffen. + - Fix version whitespace warning. + Patch by Christoph Reiter. + +* Enhancements: + - Rewrite DOS paths on native Windows builds that don't use + Cygwin/MSYS. + - Add WantDefaultPure cross-compiler personality option. + - Prefer --static --pure linking on Windows. + - Add PKG_CONFIG_DONT_DEFINE_PREFIX environment variable. + Patch by Jeff Moguillansky. + - Many improvements when building pkgconf with Meson. + Patches by Christoph Reiter. + +Changes from 1.7.2 to 1.7.3: +---------------------------- + +* Bug fixes: + - Fix a possible out of boundary write when evaluating dependencies. + Patch by Tobias Stöckmann. + - Fix escaping logic on Windows. Patch by Vincent Torri. + - Fix out of boundary reads and writes with a malformed fragment. + Patches by Tobias Stöckmann. + - Fix a possible out of boundary write when evaluating tuples. + Patch by Tobias Stöckmann. + +Changes from 1.7.1 to 1.7.2: +---------------------------- + +* Bug fixes: + - Fix a windows-specific crash relating to path fixups. + +Changes from 1.7.0 to 1.7.1: +---------------------------- + +* Bug fixes: + - Fix a possible out of boundary access in the parser for the + cross-compile database. Patch by Tobias Stöckmann. + - Missing files for building with Meson are now included in the + tarball. Patch by Neal Gompa. + - Fix calculation of package atoms on Windows with paths that + use both directory separator characters. + +Changes from 1.6.3 to 1.7.0: +---------------------------- + +* Bug fixes: + - Fix a possible buffer overflow involving newline escaping. + Patch by Tobias Stöckmann. + - Fix an out of boundary access in the parser. + Patch by Tobias Stöckmann. + - Fix leakage of strcmp() result value in pkgconf_compare_version() + responses. + - Return the default personality if loading a cross-compile + personality file failed. + - Do not complain about newlines when validating package versions. + - Properly detect strndup() on Windows when building with Meson. + +* Enhancements: + - A new --shared option and WantDefaultStatic cross-compile + configuration option have been added. This allows for toolchains + to specify that static linking should be used by default. + - Support for the PKG_CONFIG_MSVC_SYNTAX environment variable has + been added. Patch by Dan Kegel. + - Support for the PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS + client flag which disables emulation of freedesktop.org pkg-config + fragment merging semantics has been added. + Patch by Karen Arutyunov. + +Changes from 1.6.2 to 1.6.3: +---------------------------- + +* Bug fixes: + - Properly tokenize versions. Versions cannot logically contain + whitespace, as dependency-lists would not properly tokenize if + they could. A diagnostic is generated for malformed version + strings containing whitespace when --validate is used. + +* Enhancements: + - CMake support has been dropped. Use Meson to build on Windows. + +Changes from 1.6.1 to 1.6.2: +---------------------------- + +* Bug fixes: + - Fixed a memory leak when deduplicating paths. + - Fixed strndup-related build regression on Windows. + +* Enhancements: + - Added pkgconf-lite variant. pkgconf-lite is a stripped down + variant of pkgconf that only includes pkg-config features. + - Added --modversion description to pkgconf(1) man page. + +Changes from 1.6.0 to 1.6.1: +---------------------------- + +* Bug fixes: + - Fixed an issue where a personality may not be properly selected + due to argv[0] containing a full path. + - Fixed a regression where having an empty PKG_CONFIG_LIBDIR + environment variable would not eliminate the default search + paths. + - Use POSIX realpath(3) instead of readlink() for deduplicating the + search path. Use _fullpath() on Windows for the same purpose. + - The dequoting logic for tuples has been improved to ensure that + quotes *inside* a value remain quoted when necessary. + +Changes from 1.5.4 to 1.6.0: +---------------------------- + +* Bug fixes: + - Fixed issue where packages which referenced missing packages in + Requires.private may have crashed due to memory corruption issues + in some circumstances. + - Fixed warnings reported by GCC 8 diagnostics. + +* Enhancements: + - Add LIBPKGCONF_VERSION and LIBPKGCONF_VERSION_STR macros for + determining libpkgconf version. + - Add pkgconf_fragment_copy_list() to copy a fragment list to + another fragment list. + +Changes from 1.5.3 to 1.5.4: +---------------------------- + +* Bug fixes: + - fix build on Windows with Meson + - fix edge cases for path canonicalization (especially on Windows) + +Changes from 1.5.2 to 1.5.3: +---------------------------- + +* Security fixes: + - Fix edge cases involving dequoting zero-length tuples that can lead to a + buffer overflow under the right circumstances. Thanks to A. Wilcox for + reporting and supplying a patch. (MR 3) + +Changes from 1.5.1 to 1.5.2: +---------------------------- + +* Bug fixes: + - Ensure environment variables override values learned from personality files + or built-in defaults. + +* Documentation enhancements: + - Add pkgconf-personality(5) manpage documenting the personality file format. + +Changes from 1.5.0 to 1.5.1: +---------------------------- + +* Bug fixes: + - fixed a crash with some invalid multi-line .pc files + +Changes from 1.4.2 to 1.5.0: +--------------------------- + +* Administrative: + - The git repository has moved to , + due to the acquisition of GitHub by Microsoft. + +* Overall enhancements: + - pkgconf now supports the proposed Requires.internal pkg-config extension, + by merging it with the Requires.private list (there is no functional difference + between the two in our resolver implementation) + - Support for cross-compilation personalities have been added. To make use of this + functionality, create a file in the new personality.d directory that sits inside + the pkgconfig directory. The personality file format is described in + pc-personality(5). (github #166) + - Support for Haiku has been added, including interpretation of BELIBRARIES and + other toolchain specifics. (github #180) + - Testsuite support can be disabled when building with Meson. (github #175) + +* Bug fixes: + - tuples are now appropriately dequoted when added by the parser (github #186). + +* Various Windows enhancements: + - CMake supports building with GCC on Windows. (github #179) + - Prefix rewriting has been improved. (github #177) + - PKGCONF_API support has been implemented when building with Meson, + allowing Meson to be used to build pkgconf on Windows. (github #174) + +* Documentation fixes: + - The manpages have been linted and fixed. (github #181, #182, #183) + - The description of pkgconf --exists has been corrected. (github #173) + +Changes from 1.4.1 to 1.4.2: +---------------------------- + +* Bug fixes: + - ensure pkgconf_dependency_t nodes have a solution marked when satisfied + by an indirect provider (github #172) + +Changes from 1.4.0 to 1.4.1: +---------------------------- + +* Bug fixes: + - revert some quoting changes because they don't work well with certain + GCC edge cases (github #168) + +* Enhancements: + - add limited support for --cflags with --msvc-syntax + +Changes from 1.3.7 to 1.4.0: +---------------------------- + +* Notable libpkgconf API changes: + - pkgconf_pkg_t.requires has been renamed to pkgconf_pkg_t.required for + C++20 compatibility. + +* Enhancements: + - pkgconf and libpkgconf has been ported to Windows as native binaries. + - improved compatibility with freedesktop.org pkg-config's ${pc_sysrootdir} + usage pattern. + - do not mention PKG_CONFIG_SKIP_CONFLICTS environmental variable when + simplified errors are requested, as with PKG_CONFIG_PATH. + - the dependency solver now stores solutions to dependency graph elements + it visits, allowing for the dependency graph to be incrementally solved. + this improves dependency solving time by an order of magnitude in most + cases. + - new --env option allows for exporting cflags/libs fragments as export + variables + - new support for building pkgconf with CMake and Meson + - improved compiler warning flag detection on autoconf and CMake + - removed PKGCONF_BUFSIZE allocations from the stack where possible + - allow for customizing the way fragment lists are rendered using a callback API + - new support for --msvc-syntax output using the new fragment rendering callbacks + - fragments are now quoted according to POSIX literal rules + - new variables on the pkg-config builtin: + - ${pc_system_includedirs}: the system includedir search path known by pkgconf + - ${pc_system_libdirs}: the system libdir search path known by pkgconf + - new manpages: + - pc(5) describing pkgconf's interpretation of pkg-config .pc files + - pkg.m4(7) describing the autotools macros bundled with pkgconf + +* Bug fixes: + - fix pkgconf_pkg_t.id generation on native Windows where either \ or / are usable + as path separator. + - add missing --modversion to --help output + - do not evaluate module paths for modules that are not actually on disk + - ensure we work on a zeroed buffer prior to calling realpath(2) with it + - fix path deduplication edge case when cache-inodes feature is unavailable + - fix path rewriting regression with PKG_CONFIG_SYSROOT_DIR when + PKG_CONFIG_SYSROOT_DIR is set to / + - fix crash in edge case where a .pc file has misquoting in a fragment list. + - fix logic edge case when comparing relocated paths + +Changes from 1.3.6 to 1.3.7: +---------------------------- + +* Enhancements: + - improved diagnostics for malformed packages. + +* Bug fixes: + - reject packages which contain incomplete metadata in post-parse phase. + +Changes from 1.3.5 to 1.3.6: +---------------------------- + +* Enhancements: + - add many cflags to the protected set: -Wa, -Wl, -Wp, -ansi, -std=, -stdlib=, + -pedantic, -pthread, -trigraphs, -nostdinc, -nostdlibinc, -nobuiltininc. + +* Bug fixes: + - handle -include cflag fragments properly. + +Changes from 1.3.4 to 1.3.5: +---------------------------- + +* Bug fixes: + - fix --variable output for compatibility some broken configure scripts when they + request the same variable from multiple packages + +Changes from 1.3.3 to 1.3.4: +---------------------------- + +* Bug fixes: + - fix a quoting issue exposed by the Go testsuite + +Changes from 1.3.2 to 1.3.3: +---------------------------- + +* Bug fixes: + - back out disabling the dependency resolver for single-package queries, it caused + too many regressions. + +* Enhancements: + - allow explicitly disabling the dependency resolver via new environment variable, + PKG_CONFIG_MINIMUM_TRAVERSE_DEPTH=1. while pkgconf could already do this using + --minimum-traverse-depth=1, other pkg-config implementations do not have this + option, so adding an environment variable allows to make better use of this + feature (other implementations won't error due to unknown option this way) + +Changes from 1.3.1 to 1.3.2: +---------------------------- + +* Bug fixes: + - rewrite handling of --modversion, --print-variables and --variable to not require + the dependency resolver + - ensure we disable the dependency resolver in all cases where it is a single-package + query (1.3.1 did not go far enough) + +Changes from 1.3.0 to 1.3.1: +---------------------------- + +* Features: + - implement --short-errors + +* Bug fixes: + - only consider a single package at a time with --print-requires, --print-requires-private, + --print-provides, --modversion, --print-variable and --print-variables + +* Enhancements: + - synchronized latest freedesktop.org changes to pkg.m4 + - improve error reporting with legacy --atleast-version and similar flags. + +Changes from 1.2.0 to 1.3.0: +---------------------------- + +* Features: + - pkgconf --debug now provides a facility for tracing most relevant libpkgconf operations + - libpkgconf: add warn and trace handlers for warnings + - replace realpath() with faster, lighter weight path normalization function (github #112) + - pkgconf CLI now emulates pkg-config quoting rules precisely, while allowing direct access + to the actual fragments via libpkgconf + +* Bug fixes: + - pkg: properly separate static and virtual packages so they are not inappropriately + optimized out of the dependency graph (github #108) + - argvsplit: do not consider ' and " to have similar rules to escape sequences (github #111) + - pkg: strip trailing whitespace when parsing .pc files + +* Enhancements: + - argvsplit basically rewritten from scratch + - many code fixes spotted by coverity + - add PKG_CONFIG_DONT_RELOCATE_PATHS and --dont-relocate-paths environment variables to + disable path relocation feature if needed + - remove extra whitespace that was present for compatibility with older pkg-config releases + (github #113) + +Changes from 1.1.0 to 1.2.0: +---------------------------- + +* Features: + - new --path option lists the .pc files which provided the requested dependencies + - new path relocation API: pkgconf_path_relocate(), which wraps functions such as + realpath() and cygwin_conv_path(). + - new --with-path option adds a path to the search list + - new --define-prefix and --dont-define-prefix features enable automatic prefix + detection for relocatable SDKs. this is mostly useful on windows. + +* Bug fixes: + - fragments: fix even more edge cases involving token concatenation + - path lists: don't attempt to collect path inodes if the filter is disabled + - path lists: explicitly avoid uninitialised data for the path inode cache + - client: properly handle --keep-system-cflags and --keep-system-libs + +* Enhancements: + - windows: build libpkgconf as a DLL + - fragments: only munge fragments if sysroot_dir is actually set + - overall API: resolver flags have been moved to being a client-object setting + instead of used for every function invocation + +Changes from 1.0.1 to 1.1.0: +---------------------------- + +* Features: + - new Provides system allows alternate .pc files to provide a dependency + - stable library API (with documentation): http://pkgconf.readthedocs.io/ + +* Enhancements: + - make it possible to programmatically declare dependencies instead of just using the parser + - testsuite migrated to run under kyua + - provide a libpkgconf.pc file for consumers to use + - pkgconf client: new --pure flag to enable dependency graph optimization in --static mode + - significant .pc parser speedups using bsearch(3). + - handle -idirafter in the same way as -isystem CFLAGS + - learn toolchain "system" paths from GCC environment variables, if present + - filter duplicate PKG_CONFIG_PATH (and other) entries by inode + +* Bug fixes: + - fragments: fix another edge case involving empty tokens being concatenated onto previous tokens + (github #99) + - libpkgconf: remove dependencies on config.h in public headers + +Changes from 1.0.0 to 1.0.1: +---------------------------- + +* Enhancements: + - new stub implementation of --print-provides (github #95) + +* Bug fixes: + - fragments: fix an edge case involving path-only fragments and PKG_CONFIG_SYSROOT_DIR (github #94) + +Changes from 0.9.12 to 1.0.0: +----------------------------- + +* Features: + - new library: libpkgconf + +* Enhancements: + - testsuite: use an explicit prefix on all tests + - build: switch to automake + +* Bug fixes: + - cast all usage of ctype(3) functions + - do not expand variables passed via --define-variable for compatibility with pkg-config 0.29 + - let the CFLAGS being user settable + +Changes from 0.9.11 to 0.9.12: +------------------------------ + +* Features: + - add --list-package-names + +* Enhancements: + - ensure -I and -L are never pushed back + +* Bug fixes: + - fix implicit conversion warnings with variables over 31bits + +Changes from 0.9.10 to 0.9.11: +------------------------------ + +* Features: + - add --validate + +* Enhancements: + - add large file support checks in autoconf + +* Bug fixes: + - fix private lib deduplication + - handle --static correctly in some more esoteric scenarios + +Changes from 0.9.9 to 0.9.10: +----------------------------- + +* Features: + +* Enhancements: + +* Bug fixes: + - Fix parser when dealing with commented lines + +Changes from 0.9.8 to 0.9.9: +---------------------------- + +* Features: + - add a sub out --print-provides + +* Enhancements: + +* Bug fixes: + - Fix parser when dealing with comments in fields + +Changes from 0.9.7 to 0.9.8: +---------------------------- + +* Features: + +* Enhancements: + - Convert manpages to mdoc(7) + +* Bug fixes: + - Fix parsing multiline fields + +Changes from 0.9.6 to 0.9.7: +---------------------------- + +* Features: + +* Enhancements: + - Convert manpages to mdoc(7) + +* Bug fixes: + - Fix parsing multiline fields + +Changes from 0.9.5 to 0.9.6: +---------------------------- + +* Features: + - add a sub --debug + +* Enhancements: + - Do not hardcode non-posix install(1) + +* Bug fixes: + - fix --with-system-includedir and --with-system-libdir behaviour + +Changes from 0.9.4 to 0.9.5: +---------------------------- + +* Features: + +* Enhancements: + - Make all variables but CFLAGS and LIBS case sensitive + +* Bug fixes: + +Changes from 0.9.3 to 0.9.4: +---------------------------- + +* Features: + - Add a pkgconf(1) manpage + +* Enhancements: + - Improve support for MacOS -framework + +* Bug fixes: + +Changes from 0.9.2 to 0.9.3: +---------------------------- + +* Features: + - Add support for CFLAGS.private + +* Enhancements: + - Support out of source build + - Improved private libs deduplication + +* Bug fixes: + +Changes from 0.9.1 to 0.9.2: +---------------------------- + +* Features: + +* Enhancements: + +* Bug fixes: + - Fix PKG_CONFIG_PATH being ignored when a .pc is directly supplied from + command line + +Changes from 0.9.0 to 0.9.1: +---------------------------- + +* Features: + +* Enhancements: + - --simulate: print depgraph operations + - --simulate: print bytecode program as a human-readable AST + +* Bug fixes: + - reset parser state on new package atom + +Changes from 0.8.12 to 0.9.0: +---------------------------- + +* Features: + +* Enhancements: + - Rework the internal cache API + - Rework the internal code to use the new pkg_list_t framework + - Rework PKG_CONFIG_PATH handling code + +* Bug fixes: + - fix multi-recursion with -framework diff --git a/README.md b/README.md new file mode 100644 index 00000000000..257d686a62b --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# pkgconf [![test](https://github.com/pkgconf/pkgconf/actions/workflows/test.yml/badge.svg)](https://github.com/pkgconf/pkgconf/actions/workflows/test.yml) + +`pkgconf` is a program which helps to configure compiler and linker flags for +development libraries. It is a superset of the functionality provided by +pkg-config from freedesktop.org, but does not provide bug-compatibility with +the original pkg-config. + +`libpkgconf` is a library which provides access to most of `pkgconf`'s functionality, +to allow other tooling such as compilers and IDEs to discover and use libraries +configured by pkgconf. + +## release tarballs + +Release tarballs are available on [distfiles.ariadne.space][distfiles]. + + [distfiles]: https://distfiles.ariadne.space/pkgconf/ + +## build system setup + +If you would like to use the git sources directly, or a snapshot of the +sources from GitHub, you will need to regenerate the autotools build +system artifacts yourself, or use Meson instead. For example, on Alpine: + + $ apk add autoconf automake libtool build-base + $ sh ./autogen.sh + +## pkgconf-lite + +If you only need the original pkg-config functionality, there is also pkgconf-lite, +which builds the `pkgconf` frontend and relevant portions of `libpkgconf` functionality +into a single binary: + + $ make -f Makefile.lite + +## why `pkgconf` over original `pkg-config`? + +pkgconf builds a flattened directed dependency graph, which allows for more insight +into relationships between dependencies, allowing for some link-time dependency +optimization, which allows for the user to more conservatively link their binaries, +which may be helpful in some environments, such as when prelink(1) is being used. + +The solver is also optimized to handle large dependency graphs with hundreds of +thousands of edges, which can be seen in any project using the Abseil frameworks +for example. + +In addition, pkgconf has full support for virtual packages, while the original +pkg-config does not, as well as fully supporting `Conflicts` at dependency +resolution time, which is more efficient than checking for `Conflicts` while +walking the dependency graph. + +## linker flags optimization + +pkgconf, when used effectively, can make optimizations to avoid overlinking binaries. + +This functionality depends on the pkg-config module properly declaring its dependency +tree instead of using `Libs` and `Cflags` fields to directly link against other modules +which have pkg-config metadata files installed. + +The practice of using `Libs` and `Cflags` to describe unrelated dependencies is +not recommended in [Dan Nicholson's pkg-config tutorial][fd-tut] for this reason. + + [fd-tut]: http://people.freedesktop.org/~dbn/pkg-config-guide.html + +## bug compatibility with original pkg-config + +In general, we do not provide bug-level compatibility with pkg-config. + +What that means is, if you feel that there is a legitimate regression versus pkg-config, +do let us know, but also make sure that the .pc files are valid and follow the rules of +the [pkg-config tutorial][fd-tut], as most likely fixing them to follow the specified +rules will solve the problem. + +## debug output + +Please use only the stable interfaces to query pkg-config. Do not screen-scrape the +output from `--debug`: this is sent to `stderr` for a reason, it is not intended to be +scraped. The `--debug` output is **not** a stable interface, and should **never** be +depended on as a source of information. If you need a stable interface to query pkg-config +which is not covered, please get in touch. + +## compiling `pkgconf` and `libpkgconf` on UNIX + +pkgconf is basically compiled the same way any other autotools-based project is +compiled: + + $ ./configure + $ make + $ sudo make install + +If you are installing pkgconf into a custom prefix, such as `/opt/pkgconf`, you will +likely want to define the default system includedir and libdir for your toolchain. +To do this, use the `--with-system-includedir` and `--with-system-libdir` configure +flags like so: + + $ ./configure \ + --prefix=/opt/pkgconf \ + --with-system-libdir=/lib:/usr/lib \ + --with-system-includedir=/usr/include + $ make + $ sudo make install + +## compiling `pkgconf` and `libpkgconf` with Meson (usually for Windows) + +pkgconf is compiled using [Meson](https://mesonbuild.com) on Windows. In theory, you could also use +Meson to build on UNIX, but this is not recommended at this time as pkgconf is typically built +much earlier than Meson. + + $ meson setup build -Dtests=disabled + $ meson compile -C build + $ meson install -C build + +There are a few defines such as `SYSTEM_LIBDIR`, `PKGCONFIGDIR` and `SYSTEM_INCLUDEDIR`. +However, on Windows, the default `PKGCONFIGDIR` value is usually overridden at runtime based +on path relocation. + +## pkg-config symlink + +If you want pkgconf to be used when you invoke `pkg-config`, you should install a +symlink for this. We do not do this for you, as we believe it is better for vendors +to make this determination themselves. + + $ ln -sf pkgconf /usr/bin/pkg-config + +## contacts + +You can report bugs at . + +There is a mailing list at . + +You can contact us via IRC at `#pkgconf` at `irc.oftc.net`. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000000..92bd5b67897 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,87 @@ +#! /bin/sh + +TOP_DIR=$(dirname $0) +LAST_DIR=$PWD + +if test ! -f $TOP_DIR/configure.ac ; then + echo "You must execute this script from the top level directory." + exit 1 +fi + +AUTOCONF=${AUTOCONF:-autoconf} +ACLOCAL=${ACLOCAL:-aclocal} +AUTOHEADER=${AUTOHEADER:-autoheader} +AUTOMAKE=${AUTOMAKE:-automake} +LIBTOOLIZE=${LIBTOOLIZE:-libtoolize} + +dump_help_screen () +{ + echo "Usage: $0 [options]" + echo + echo "options:" + echo " -n skip CVS changelog creation" + echo " -h,--help show this help screen" + echo + exit 0 +} + +parse_options () +{ + while test "$1" != "" ; do + case $1 in + -h|--help) + dump_help_screen + ;; + -n) + SKIP_CVS_CHANGELOG=yes + ;; + *) + echo Invalid argument - $1 + dump_help_screen + ;; + esac + shift + done +} + +run_or_die () +{ + COMMAND=$1 + + # check for empty commands + if test -z "$COMMAND" ; then + echo "*warning* no command specified" + return 1 + fi + + shift; + + OPTIONS="$@" + + # print a message + echo -n "*info* running $COMMAND" + if test -n "$OPTIONS" ; then + echo " ($OPTIONS)" + else + echo + fi + + # run or die + $COMMAND $OPTIONS ; RESULT=$? + if test $RESULT -ne 0 ; then + echo "*error* $COMMAND failed. (exit code = $RESULT)" + exit 1 + fi + + return 0 +} + +parse_options "$@" + +cd $TOP_DIR + +run_or_die $ACLOCAL +run_or_die $AUTOHEADER +run_or_die $AUTOCONF +run_or_die $LIBTOOLIZE --install +run_or_die $AUTOMAKE --add-missing diff --git a/cli/bomtool/main.c b/cli/bomtool/main.c new file mode 100644 index 00000000000..1e8391aa5ae --- /dev/null +++ b/cli/bomtool/main.c @@ -0,0 +1,368 @@ +/* + * bomtool/main.c + * main() routine, printer functions + * + * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + * pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "libpkgconf/config.h" +#include +#include +#include "getopt_long.h" + +#define PKG_VERSION (((uint64_t) 1) << 1) +#define PKG_ABOUT (((uint64_t) 1) << 2) +#define PKG_HELP (((uint64_t) 1) << 3) + +static const char *spdx_version = "SPDX-2.2"; +static const char *bom_license = "CC0-1.0"; +static const char *document_ref = "SPDXRef-DOCUMENT"; + +static pkgconf_client_t pkg_client; +static uint64_t want_flags; +static size_t maximum_package_count = 0; +static int maximum_traverse_depth = 2000; +FILE *error_msgout = NULL; + +static bool +error_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) client; + (void) data; + fprintf(error_msgout, "%s", msg); + return true; +} + +static const char * +sbom_spdx_identity(pkgconf_pkg_t *pkg) +{ + static char buf[PKGCONF_ITEM_SIZE]; + + snprintf(buf, sizeof buf, "%sC64%s", pkg->id, pkg->version); + + return buf; +} + +static const char * +sbom_name(pkgconf_pkg_t *world) +{ + static char buf[PKGCONF_BUFSIZE]; + pkgconf_node_t *node; + + pkgconf_strlcpy(buf, "SBOM-SPDX", sizeof buf); + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (!dep->match) + continue; + + pkgconf_strlcat(buf, "-", sizeof buf); + pkgconf_strlcat(buf, sbom_spdx_identity(match), sizeof buf); + } + + return buf; +} + +static void +write_sbom_header(pkgconf_client_t *client, pkgconf_pkg_t *world) +{ + (void) client; + (void) world; + + printf("SPDXVersion: %s\n", spdx_version); + printf("DataLicense: %s\n", bom_license); + printf("SPDXID: %s\n", document_ref); + printf("DocumentName: %s\n", sbom_name(world)); + printf("DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION); + printf("Creator: Tool: bomtool %s\n", PACKAGE_VERSION); + + printf("\n\n"); +} + +static const char * +sbom_identity(pkgconf_pkg_t *pkg) +{ + static char buf[PKGCONF_ITEM_SIZE]; + + snprintf(buf, sizeof buf, "%s@%s", pkg->id, pkg->version); + + return buf; +} + +static void +write_sbom_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) +{ + (void) client; + (void) unused; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + printf("##### Package: %s\n\n", sbom_identity(pkg)); + + printf("PackageName: %s\n", sbom_identity(pkg)); + printf("SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg)); + printf("PackageVersion: %s\n", pkg->version); + printf("PackageDownloadLocation: NOASSERTION\n"); + printf("PackageVerificationCode: NOASSERTION\n"); + + /* XXX: What about projects? */ + if (pkg->maintainer != NULL) + printf("PackageSupplier: Person: %s\n", pkg->maintainer); + + if (pkg->url != NULL) + printf("PackageHomePage: %s\n", pkg->url); + + printf("PackageLicenseDeclared: %s\n", pkg->license != NULL ? pkg->license : "NOASSERTION"); + + if (pkg->copyright != NULL) + printf("PackageCopyrightText: %s\n", pkg->copyright); + + if (pkg->description != NULL) + printf("PackageSummary: %s\n", pkg->description); + + printf("\n\n"); +} + +static void +write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) +{ + (void) client; + (void) unused; + + char baseref[PKGCONF_ITEM_SIZE]; + pkgconf_node_t *node; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + snprintf(baseref, sizeof baseref, "SPDXRef-Package-%sC64%s", pkg->id, pkg->version); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + + if (!dep->match) + continue; + + printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); + printf("Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + + if (!dep->match) + continue; + + printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match)); + printf("Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref); + } + + if (pkg->required.head != NULL || pkg->requires_private.head != NULL) + printf("\n\n"); +} + +static bool +generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world) +{ + int eflag; + pkgconf_node_t *node; + + write_sbom_header(client, world); + + eflag = pkgconf_pkg_traverse(client, world, write_sbom_package, NULL, maximum_traverse_depth, 0); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + eflag = pkgconf_pkg_traverse(client, world, write_sbom_relationships, NULL, maximum_traverse_depth, 0); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_pkg_t *match = dep->match; + + if (!dep->match) + continue; + + printf("Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match)); + } + + return true; +} + +static int +version(void) +{ + printf("bomtool %s\n", PACKAGE_VERSION); + return EXIT_SUCCESS; +} + +static int +about(void) +{ + printf("bomtool (%s %s)\n", PACKAGE_NAME, PACKAGE_VERSION); + printf("Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021\n"); + printf(" pkgconf authors (see AUTHORS in documentation directory).\n\n"); + printf("Permission to use, copy, modify, and/or distribute this software for any\n"); + printf("purpose with or without fee is hereby granted, provided that the above\n"); + printf("copyright notice and this permission notice appear in all copies.\n\n"); + printf("This software is provided 'as is' and without any warranty, express or\n"); + printf("implied. In no event shall the authors be liable for any damages arising\n"); + printf("from the use of this software.\n\n"); + printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT); + return EXIT_SUCCESS; +} + +static int +usage(void) +{ + printf("usage: bomtool [--flags] [modules]\n"); + + printf("\nbasic options:\n\n"); + + printf(" --help this message\n"); + printf(" --about print bomtool version and license to stdout\n"); + printf(" --version print bomtool version to stdout\n"); + + return EXIT_SUCCESS; +} + +int +main(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default(); + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + error_msgout = stderr; + + struct pkg_option options[] = { + { "version", no_argument, &want_flags, PKG_VERSION, }, + { "about", no_argument, &want_flags, PKG_ABOUT, }, + { "help", no_argument, &want_flags, PKG_HELP, }, + { NULL, 0, NULL, 0 } + }; + + while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1) + { + switch (ret) + { + case '?': + case ':': + return EXIT_FAILURE; + default: + break; + } + } + + pkgconf_client_init(&pkg_client, error_handler, NULL, personality); + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&pkg_client, personality); + + if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + return about(); + + if ((want_flags & PKG_VERSION) == PKG_VERSION) + return version(); + + if ((want_flags & PKG_HELP) == PKG_HELP) + return usage(); + + while (1) + { + const char *package = argv[pkg_optind]; + + if (package == NULL) + break; + + /* check if there is a limit to the number of packages allowed to be included, if so and we have hit + * the limit, stop adding packages to the queue. + */ + if (maximum_package_count > 0 && pkgq.length > maximum_package_count) + break; + + while (isspace((unsigned char)package[0])) + package++; + + /* skip empty packages */ + if (package[0] == '\0') { + pkg_optind++; + continue; + } + + if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) + { + pkgconf_queue_push(&pkgq, package); + pkg_optind++; + } + else + { + char packagebuf[PKGCONF_BUFSIZE]; + + snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); + pkg_optind += 3; + + pkgconf_queue_push(&pkgq, packagebuf); + } + } + + if (pkgq.head == NULL) + { + fprintf(stderr, "Please specify at least one package name on the command line.\n"); + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + + if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + if (!generate_sbom_from_world(&pkg_client, &world)) + { + ret = EXIT_FAILURE; + goto out; + } + +out: + pkgconf_solution_free(&pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cross_personality_deinit(personality); + pkgconf_client_deinit(&pkg_client); + + return ret; +} diff --git a/cli/getopt_long.c b/cli/getopt_long.c new file mode 100644 index 00000000000..c47215fae4c --- /dev/null +++ b/cli/getopt_long.c @@ -0,0 +1,643 @@ +/* $OpenBSD: getopt_long.c,v 1.21 2006/09/22 17:22:05 millert Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "getopt_long.h" + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#define PKGCONF_HACK_LOGICAL_OR_ALL_VALUES + +int pkg_opterr = 1; /* if error message should be printed */ +int pkg_optind = 1; /* index into parent argv vector */ +int pkg_optopt = '?'; /* character checked for validity */ +int pkg_optreset; /* reset getopt */ +char *pkg_optarg; /* argument associated with option */ + +#define PRINT_ERROR ((pkg_opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +/* add some padding to EMSG to avoid overrun */ +#define EMSG "\0\0\0\0" + +#ifdef GNU_COMPATIBLE +#define NO_PREFIX (-1) +#define D_PREFIX 0 +#define DD_PREFIX 1 +#define W_PREFIX 2 +#endif + +static int getopt_internal(int, char * const *, const char *, + const struct pkg_option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct pkg_option *, int *, int, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set pkg_optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ +#ifdef GNU_COMPATIBLE +static int dash_prefix = NO_PREFIX; +static const char gnuoptchar[] = "invalid option -- %c"; + +static const char recargstring[] = "option `%s%s' requires an argument"; +static const char ambig[] = "option `%s%.*s' is ambiguous"; +static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; +static const char illoptstring[] = "unrecognized option `%s%s'"; +#else +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptstring[] = "unknown option -- %s"; +#endif + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct pkg_option *long_options, int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; +#ifdef GNU_COMPATIBLE + char *current_dash; +#endif + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = place; +#ifdef GNU_COMPATIBLE + switch (dash_prefix) { + case D_PREFIX: + current_dash = "-"; + break; + case DD_PREFIX: + current_dash = "--"; + break; + case W_PREFIX: + current_dash = "-W "; + break; + default: + current_dash = ""; + break; + } +#endif + match = -1; + exact_match = 0; + second_partial_match = 0; + + pkg_optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != + long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, ambig, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + fprintf(stderr, "\n"); + } + pkg_optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, noarg, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + fprintf(stderr, "\n"); + } + /* + * XXX: GNU sets pkg_optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + pkg_optopt = (int)long_options[match].val; + else + pkg_optopt = 0; +#ifdef GNU_COMPATIBLE + return (BADCH); +#else + return (BADARG); +#endif + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + pkg_optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + pkg_optarg = nargv[pkg_optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (pkg_optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, recargstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + fprintf(stderr, "\n"); + } + /* + * XXX: GNU sets pkg_optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + pkg_optopt = (int)long_options[match].val; + else + pkg_optopt = 0; + --pkg_optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --pkg_optind; + return (-1); + } + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, illoptstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + fprintf(stderr, "\n"); + } + pkg_optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { +#ifdef PKGCONF_HACK_LOGICAL_OR_ALL_VALUES + *long_options[match].flag |= long_options[match].val; +#else + *long_options[match].flag = long_options[match].val; +#endif + return (0); + } else + return ((int)long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct pkg_option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + int posixly_correct; /* no static, can be changed on the fly */ + + if (options == NULL) + return (-1); + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); +#ifdef GNU_COMPATIBLE + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; +#else + if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; +#endif + + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set pkg_optind to 0 instead of + * XXX using pkg_optreset. Work around this braindamage. + */ + if (pkg_optind == 0) + pkg_optind = pkg_optreset = 1; + + pkg_optarg = NULL; + if (pkg_optreset) + nonopt_start = nonopt_end = -1; +start: + if (pkg_optreset || !*place) { /* update scanning pointer */ + pkg_optreset = 0; + if (pkg_optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + pkg_optind, nargv); + pkg_optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set pkg_optind + * to the first of them. + */ + pkg_optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[pkg_optind]) != '-' || +#ifdef GNU_COMPATIBLE + place[1] == '\0') { +#else + (place[1] == '\0' && strchr(options, '-') == NULL)) { +#endif + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + pkg_optarg = nargv[pkg_optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = pkg_optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + pkg_optind, nargv); + nonopt_start = pkg_optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + pkg_optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = pkg_optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + pkg_optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + pkg_optind, nargv); + pkg_optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[pkg_optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; +#ifdef GNU_COMPATIBLE + dash_prefix = D_PREFIX; +#endif + if (*place == '-') { + place++; /* --foo long option */ +#ifdef GNU_COMPATIBLE + dash_prefix = DD_PREFIX; +#endif + } else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too, flags); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++pkg_optind; +#ifdef GNU_COMPATIBLE + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, posixly_correct ? illoptchar : gnuoptchar, + optchar); + fprintf(stderr, "\n"); + } +#else + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, illoptchar, optchar); + fprintf(stderr, "\n"); + } +#endif + pkg_optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++pkg_optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, recargchar, optchar); + fprintf(stderr, "\n"); + } + pkg_optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[pkg_optind]; +#ifdef GNU_COMPATIBLE + dash_prefix = W_PREFIX; +#endif + optchar = parse_long_options(nargv, options, long_options, + idx, 0, flags); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++pkg_optind; + } else { /* takes (optional) argument */ + pkg_optarg = NULL; + if (*place) /* no white space */ + pkg_optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++pkg_optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) { + fprintf(stderr, "pkgconf: "); + fprintf(stderr, recargchar, optchar); + fprintf(stderr, "\n"); + } + pkg_optopt = optchar; + return (BADARG); + } else + pkg_optarg = nargv[pkg_optind]; + } + place = EMSG; + ++pkg_optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +pkg_getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +pkg_getopt_long(int nargc, char * const *nargv, const char *options, + const struct pkg_option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +pkg_getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct pkg_option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} diff --git a/cli/getopt_long.h b/cli/getopt_long.h new file mode 100644 index 00000000000..b1242915ab5 --- /dev/null +++ b/cli/getopt_long.h @@ -0,0 +1,70 @@ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GETOPT_LONG_H_ +#define _GETOPT_LONG_H_ + +#include + +/* + * GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension. + * getopt() is declared here too for GNU programs. + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct pkg_option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + uint64_t *flag; + /* if flag not NULL, value to set *flag to; else return value */ + uint64_t val; +}; + +int pkg_getopt_long(int, char * const *, const char *, + const struct pkg_option *, int *); +int pkg_getopt_long_only(int, char * const *, const char *, + const struct pkg_option *, int *); +int pkg_getopt(int, char * const [], const char *); + +extern char *pkg_optarg; /* getopt(3) external variables */ +extern int pkg_optind, pkg_opterr, pkg_optopt; +extern int pkg_optreset; /* getopt(3) external variable */ + +#endif diff --git a/cli/main.c b/cli/main.c new file mode 100644 index 00000000000..53eee4c8744 --- /dev/null +++ b/cli/main.c @@ -0,0 +1,1823 @@ +/* + * main.c + * main() routine, printer functions + * + * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + * pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include "libpkgconf/config.h" +#include +#include +#include "getopt_long.h" +#ifndef PKGCONF_LITE +#include "renderer-msvc.h" +#endif +#ifdef _WIN32 +#include /* for _setmode() */ +#include +#endif + +#define PKG_CFLAGS_ONLY_I (((uint64_t) 1) << 2) +#define PKG_CFLAGS_ONLY_OTHER (((uint64_t) 1) << 3) +#define PKG_CFLAGS (PKG_CFLAGS_ONLY_I|PKG_CFLAGS_ONLY_OTHER) +#define PKG_LIBS_ONLY_LDPATH (((uint64_t) 1) << 5) +#define PKG_LIBS_ONLY_LIBNAME (((uint64_t) 1) << 6) +#define PKG_LIBS_ONLY_OTHER (((uint64_t) 1) << 7) +#define PKG_LIBS (PKG_LIBS_ONLY_LDPATH|PKG_LIBS_ONLY_LIBNAME|PKG_LIBS_ONLY_OTHER) +#define PKG_MODVERSION (((uint64_t) 1) << 8) +#define PKG_REQUIRES (((uint64_t) 1) << 9) +#define PKG_REQUIRES_PRIVATE (((uint64_t) 1) << 10) +#define PKG_VARIABLES (((uint64_t) 1) << 11) +#define PKG_DIGRAPH (((uint64_t) 1) << 12) +#define PKG_KEEP_SYSTEM_CFLAGS (((uint64_t) 1) << 13) +#define PKG_KEEP_SYSTEM_LIBS (((uint64_t) 1) << 14) +#define PKG_VERSION (((uint64_t) 1) << 15) +#define PKG_ABOUT (((uint64_t) 1) << 16) +#define PKG_ENV_ONLY (((uint64_t) 1) << 17) +#define PKG_ERRORS_ON_STDOUT (((uint64_t) 1) << 18) +#define PKG_SILENCE_ERRORS (((uint64_t) 1) << 19) +#define PKG_IGNORE_CONFLICTS (((uint64_t) 1) << 20) +#define PKG_STATIC (((uint64_t) 1) << 21) +#define PKG_NO_UNINSTALLED (((uint64_t) 1) << 22) +#define PKG_UNINSTALLED (((uint64_t) 1) << 23) +#define PKG_LIST (((uint64_t) 1) << 24) +#define PKG_HELP (((uint64_t) 1) << 25) +#define PKG_PRINT_ERRORS (((uint64_t) 1) << 26) +#define PKG_SIMULATE (((uint64_t) 1) << 27) +#define PKG_NO_CACHE (((uint64_t) 1) << 28) +#define PKG_PROVIDES (((uint64_t) 1) << 29) +#define PKG_VALIDATE (((uint64_t) 1) << 30) +#define PKG_LIST_PACKAGE_NAMES (((uint64_t) 1) << 31) +#define PKG_NO_PROVIDES (((uint64_t) 1) << 32) +#define PKG_PURE (((uint64_t) 1) << 33) +#define PKG_PATH (((uint64_t) 1) << 34) +#define PKG_DEFINE_PREFIX (((uint64_t) 1) << 35) +#define PKG_DONT_DEFINE_PREFIX (((uint64_t) 1) << 36) +#define PKG_DONT_RELOCATE_PATHS (((uint64_t) 1) << 37) +#define PKG_DEBUG (((uint64_t) 1) << 38) +#define PKG_SHORT_ERRORS (((uint64_t) 1) << 39) +#define PKG_EXISTS (((uint64_t) 1) << 40) +#define PKG_MSVC_SYNTAX (((uint64_t) 1) << 41) +#define PKG_INTERNAL_CFLAGS (((uint64_t) 1) << 42) +#define PKG_DUMP_PERSONALITY (((uint64_t) 1) << 43) +#define PKG_SHARED (((uint64_t) 1) << 44) +#define PKG_DUMP_LICENSE (((uint64_t) 1) << 45) +#define PKG_SOLUTION (((uint64_t) 1) << 46) +#define PKG_EXISTS_CFLAGS (((uint64_t) 1) << 47) +#define PKG_FRAGMENT_TREE (((uint64_t) 1) << 48) + +static pkgconf_client_t pkg_client; +static const pkgconf_fragment_render_ops_t *want_render_ops = NULL; + +static uint64_t want_flags; +static int verbosity = 0; +static int maximum_traverse_depth = 2000; +static size_t maximum_package_count = 0; + +static char *want_variable = NULL; +static char *want_fragment_filter = NULL; + +FILE *error_msgout = NULL; +FILE *logfile_out = NULL; + +static bool +error_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) client; + (void) data; + fprintf(error_msgout, "%s", msg); + return true; +} + +static bool +print_list_entry(const pkgconf_pkg_t *entry, void *data) +{ + (void) data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + printf("%-30s %s - %s\n", entry->id, entry->realname, entry->description); + + return false; +} + +static bool +print_package_entry(const pkgconf_pkg_t *entry, void *data) +{ + (void) data; + + if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + return false; + + printf("%s\n", entry->id); + + return false; +} + +static bool +filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + (void) client; + (void) data; + + if (!(want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + if (frag->type == 'I') + got_flags = PKG_CFLAGS_ONLY_I; + else + got_flags = PKG_CFLAGS_ONLY_OTHER; + + return (want_flags & got_flags) != 0; +} + +static bool +filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data) +{ + int got_flags = 0; + (void) client; + (void) data; + + if (!(want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag)) + return false; + + if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type)) + return false; + + switch (frag->type) + { + case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break; + case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break; + default: got_flags = PKG_LIBS_ONLY_OTHER; break; + } + + return (want_flags & got_flags) != 0; +} + +static void +print_variables(pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node) + { + pkgconf_tuple_t *tuple = node->data; + + printf("%s\n", tuple->key); + } +} + +static void +print_requires(pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + + printf("%s", dep->package); + + if (dep->version != NULL) + printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); + + printf("\n"); + } +} + +static void +print_requires_private(pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) + { + pkgconf_dependency_t *dep = node->data; + + printf("%s", dep->package); + + if (dep->version != NULL) + printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); + + printf("\n"); + } +} + +static void +print_provides(pkgconf_pkg_t *pkg) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node) + { + pkgconf_dependency_t *dep = node->data; + + printf("%s", dep->package); + + if (dep->version != NULL) + printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version); + + printf("\n"); + } +} + +static bool +apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) client; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_provides(pkg); + } + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_node_t *node; + (void) client; + pkgconf_pkg_t **last_seen = data; + + if(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + printf("\"%s\" [fontname=Sans fontsize=8 fontcolor=gray color=gray]\n", pkg->id); + else + printf("\"%s\" [fontname=Sans fontsize=8]\n", pkg->id); + + if (last_seen != NULL) + { + if (*last_seen != NULL) + printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id); + + *last_seen = pkg; + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + if ((dep->flags & PKGCONF_PKG_DEPF_PRIVATE) == 0) + printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg->id, dep_id); + else + printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); + } + + PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node) + { + pkgconf_dependency_t *dep = node->data; + const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package; + + printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id); + } +} + +static bool +apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + pkgconf_list_t *list = data; + pkgconf_pkg_t *last_seen = NULL; + pkgconf_node_t *iter; + + printf("digraph deptree {\n"); + printf("edge [color=blue len=7.5 fontname=Sans fontsize=8]\n"); + printf("node [fontname=Sans fontsize=8]\n"); + printf("\"user:request\" [fontname=Sans fontsize=8]\n"); + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + pkgconf_queue_t *pkgq = iter->data; + pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package); + printf("\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg == NULL ? pkgq->package : pkg->id); + if (pkg != NULL) + pkgconf_pkg_unref(client, pkg); + } + + eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + printf("}\n"); + return true; +} + +static void +print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused) +{ + (void) client; + (void) unused; + + printf("%s (%"PRIu64")%s\n", pkg->id, pkg->identifier, (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : ""); +} + +static bool +apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0); + + return eflag == PKGCONF_PKG_ERRF_OK; +} +#endif + +static bool +apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_node_t *queue_iter; + pkgconf_list_t *pkgq = data; + (void) client; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter) + { + pkgconf_node_t *world_iter; + pkgconf_queue_t *queue_node = queue_iter->data; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + + const size_t name_len = strlen(pkg->why); + if (name_len > strlen(queue_node->package) || + strncmp(pkg->why, queue_node->package, name_len) || + (queue_node->package[name_len] != 0 && + !isspace((unsigned char)queue_node->package[name_len]) && + !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len]))) + continue; + + if (pkg->version != NULL) { + if (verbosity) + printf("%s: ", pkg->id); + + printf("%s\n", pkg->version); + } + + break; + } + } + + return true; +} + +static bool +apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) client; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_variables(pkg); + } + + return true; +} + +static bool +apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) client; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + /* a module entry with no filename is either virtual, static (builtin) or synthesized. */ + if (pkg->filename != NULL) + printf("%s\n", pkg->filename); + } + + return true; +} + +static bool +apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, void *variable, int maxdepth) +{ + pkgconf_node_t *iter; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + const char *var; + + var = pkgconf_tuple_find(client, &pkg->vars, variable); + + if (var != NULL) + printf("%s%s", iter->prev != NULL ? " " : "", var); + } + + printf("\n"); + + return true; +} + +static bool +apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth, + unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth), + bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data), + void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + unsigned int eflag; + char *render_buf; + + eflag = collect_fn(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_fn, NULL); + + if (postprocess_fn != NULL) + postprocess_fn(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); + printf("%s='%s'\n", prefix, render_buf); + free(render_buf); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static void +maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list) +{ + pkgconf_node_t *world_iter; + + if ((want_flags & PKG_EXISTS_CFLAGS) != PKG_EXISTS_CFLAGS) + return; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + snprintf(havebuf, sizeof havebuf, "HAVE_%s", dep->match->id); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = toupper((unsigned char) *p); + } + } + + pkgconf_fragment_insert(client, fragment_list, 'D', havebuf, false); + } +} + +static void +apply_env_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *env_prefix) +{ + (void) client; + pkgconf_node_t *world_iter; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter) + { + pkgconf_dependency_t *dep = world_iter->data; + pkgconf_pkg_t *pkg = dep->match; + pkgconf_node_t *tuple_iter; + + if ((dep->flags & PKGCONF_PKG_DEPF_QUERY) != PKGCONF_PKG_DEPF_QUERY) + continue; + + if (dep->match == NULL) + continue; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, tuple_iter) + { + pkgconf_tuple_t *tuple = tuple_iter->data; + char havebuf[PKGCONF_ITEM_SIZE]; + char *p; + + if (want_variable != NULL && strcmp(want_variable, tuple->key)) + continue; + + snprintf(havebuf, sizeof havebuf, "%s_%s", env_prefix, tuple->key); + + for (p = havebuf; *p; p++) + { + switch (*p) + { + case ' ': + case '-': + *p = '_'; + break; + + default: + *p = toupper((unsigned char) *p); + } + } + + printf("%s='%s'\n", havebuf, tuple->value); + } + } +} + +static bool +apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, void *env_prefix_p, int maxdepth) +{ + const char *want_env_prefix = env_prefix_p, *it; + char workbuf[PKGCONF_ITEM_SIZE]; + + for (it = want_env_prefix; *it != '\0'; it++) + if (!isalpha((unsigned char)*it) && + !isdigit((unsigned char)*it)) + return false; + + snprintf(workbuf, sizeof workbuf, "%s_CFLAGS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_cflags, filter_cflags, maybe_add_module_definitions)) + return false; + + snprintf(workbuf, sizeof workbuf, "%s_LIBS", want_env_prefix); + if (!apply_env_var(workbuf, client, world, maxdepth, pkgconf_pkg_libs, filter_libs, NULL)) + return false; + + if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || want_variable != NULL) + apply_env_variables(client, world, want_env_prefix); + + return true; +} + +static bool +apply_cflags(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + char *render_buf; + (void) unused; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_cflags, NULL); + maybe_add_module_definitions(client, world, &filtered_list); + + if (filtered_list.head == NULL) + goto out; + + render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); + printf("%s", render_buf); + free(render_buf); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_libs(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + char *render_buf; + (void) unused; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_libs, NULL); + + if (filtered_list.head == NULL) + goto out; + + render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops); + printf("%s", render_buf); + free(render_buf); + +out: + pkgconf_fragment_free(&unfiltered_list); + pkgconf_fragment_free(&filtered_list); + + return true; +} + +static bool +apply_requires(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) client; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_requires(pkg); + } + + return true; +} + +static bool +apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth) +{ + pkgconf_node_t *iter; + (void) client; + (void) unused; + (void) maxdepth; + + PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter) + { + pkgconf_dependency_t *dep = iter->data; + pkgconf_pkg_t *pkg = dep->match; + + print_requires_private(pkg); + } + return true; +} + +static void +check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + int *retval = data; + (void) client; + + if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + *retval = EXIT_SUCCESS; +} + +static bool +apply_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +#ifndef PKGCONF_LITE +static void +print_graph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_node_t *n; + + (void) client; + (void) data; + + printf("node '%s' {\n", pkg->id); + + if (pkg->version != NULL) + printf(" version = '%s';\n", pkg->version); + + PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n) + { + pkgconf_dependency_t *dep = n->data; + printf(" dependency '%s'", dep->package); + if (dep->compare != PKGCONF_CMP_ANY) + { + printf(" {\n"); + printf(" comparator = '%s';\n", pkgconf_pkg_get_comparator(dep)); + printf(" version = '%s';\n", dep->version); + printf(" };\n"); + } + else + printf(";\n"); + } + + printf("};\n"); +} + +static bool +apply_simulate(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} +#endif + +static void +print_fragment_tree_branch(pkgconf_list_t *fragment_list, int indent) +{ + pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(fragment_list->head, iter) + { + pkgconf_fragment_t *frag = iter->data; + + if (frag->type) + printf("%*s'-%c%s' [type %c]\n", indent, "", frag->type, frag->data, frag->type); + else + printf("%*s'%s' [untyped]\n", indent, "", frag->data); + + print_fragment_tree_branch(&frag->children, indent + 2); + } + + if (fragment_list->head != NULL) + printf("\n"); +} + +static bool +apply_fragment_tree(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER; + int eflag; + + (void) data; + + eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth); + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + print_fragment_tree_branch(&unfiltered_list, 0); + pkgconf_fragment_free(&unfiltered_list); + + return true; +} + +static void +print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + (void) client; + (void) data; + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + /* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */ + printf("%s: %s\n", pkg->id, pkg->license != NULL ? pkg->license : "NOASSERTION"); +} + +static bool +apply_license(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth) +{ + int eflag; + + eflag = pkgconf_pkg_traverse(client, world, print_license, data, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + return false; + + return true; +} + +static void +version(void) +{ + printf("%s\n", PACKAGE_VERSION); +} + +static void +about(void) +{ + printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); + printf("Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021\n"); + printf(" pkgconf authors (see AUTHORS in documentation directory).\n\n"); + printf("Permission to use, copy, modify, and/or distribute this software for any\n"); + printf("purpose with or without fee is hereby granted, provided that the above\n"); + printf("copyright notice and this permission notice appear in all copies.\n\n"); + printf("This software is provided 'as is' and without any warranty, express or\n"); + printf("implied. In no event shall the authors be liable for any damages arising\n"); + printf("from the use of this software.\n\n"); + printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT); +} + +static void +usage(void) +{ + printf("usage: %s [OPTIONS] [LIBRARIES]\n", PACKAGE_NAME); + + printf("\nbasic options:\n\n"); + + printf(" --help this message\n"); + printf(" --about print pkgconf version and license to stdout\n"); + printf(" --version print supported pkg-config version to stdout\n"); + printf(" --verbose print additional information\n"); + printf(" --atleast-pkgconfig-version check whether or not pkgconf is compatible\n"); + printf(" with a specified pkg-config version\n"); + printf(" --errors-to-stdout print all errors on stdout instead of stderr\n"); + printf(" --print-errors ensure all errors are printed\n"); + printf(" --short-errors be less verbose about some errors\n"); + printf(" --silence-errors explicitly be silent about errors\n"); + printf(" --list-all list all known packages\n"); + printf(" --list-package-names list all known package names\n"); +#ifndef PKGCONF_LITE + printf(" --simulate simulate walking the calculated dependency graph\n"); +#endif + printf(" --no-cache do not cache already seen packages when\n"); + printf(" walking the dependency graph\n"); + printf(" --log-file=filename write an audit log to a specified file\n"); + printf(" --with-path=path adds a directory to the search path\n"); + printf(" --define-prefix override the prefix variable with one that is guessed based on\n"); + printf(" the location of the .pc file\n"); + printf(" --dont-define-prefix do not override the prefix variable under any circumstances\n"); + printf(" --prefix-variable=varname sets the name of the variable that pkgconf considers\n"); + printf(" to be the package prefix\n"); + printf(" --relocate=path relocates a path and exits (mostly for testsuite)\n"); + printf(" --dont-relocate-paths disables path relocation support\n"); + +#ifndef PKGCONF_LITE + printf("\ncross-compilation personality support:\n\n"); + printf(" --personality=triplet|filename sets the personality to 'triplet' or a file named 'filename'\n"); + printf(" --dump-personality dumps details concerning selected personality\n"); +#endif + + printf("\nchecking specific pkg-config database entries:\n\n"); + + printf(" --atleast-version require a specific version of a module\n"); + printf(" --exact-version require an exact version of a module\n"); + printf(" --max-version require a maximum version of a module\n"); + printf(" --exists check whether or not a module exists\n"); + printf(" --uninstalled check whether or not an uninstalled module will be used\n"); + printf(" --no-uninstalled never use uninstalled modules when satisfying dependencies\n"); + printf(" --no-provides do not use 'provides' rules to resolve dependencies\n"); + printf(" --maximum-traverse-depth maximum allowed depth for dependency graph\n"); + printf(" --static be more aggressive when computing dependency graph\n"); + printf(" (for static linking)\n"); + printf(" --shared use a simplified dependency graph (usually default)\n"); + printf(" --pure optimize a static dependency graph as if it were a normal\n"); + printf(" dependency graph\n"); + printf(" --env-only look only for package entries in PKG_CONFIG_PATH\n"); + printf(" --ignore-conflicts ignore 'conflicts' rules in modules\n"); + printf(" --validate validate specific .pc files for correctness\n"); + + printf("\nquerying specific pkg-config database fields:\n\n"); + + printf(" --define-variable=varname=value define variable 'varname' as 'value'\n"); + printf(" --variable=varname print specified variable entry to stdout\n"); + printf(" --cflags print required CFLAGS to stdout\n"); + printf(" --cflags-only-I print required include-dir CFLAGS to stdout\n"); + printf(" --cflags-only-other print required non-include-dir CFLAGS to stdout\n"); + printf(" --libs print required linker flags to stdout\n"); + printf(" --libs-only-L print required LDPATH linker flags to stdout\n"); + printf(" --libs-only-l print required LIBNAME linker flags to stdout\n"); + printf(" --libs-only-other print required other linker flags to stdout\n"); + printf(" --print-requires print required dependency frameworks to stdout\n"); + printf(" --print-requires-private print required dependency frameworks for static\n"); + printf(" linking to stdout\n"); + printf(" --print-provides print provided dependencies to stdout\n"); + printf(" --print-variables print all known variables in module to stdout\n"); +#ifndef PKGCONF_LITE + printf(" --digraph print entire dependency graph in graphviz 'dot' format\n"); + printf(" --solution print dependency graph solution in a simple format\n"); +#endif + printf(" --keep-system-cflags keep -I%s entries in cflags output\n", SYSTEM_INCLUDEDIR); + printf(" --keep-system-libs keep -L%s entries in libs output\n", SYSTEM_LIBDIR); + printf(" --path show the exact filenames for any matching .pc files\n"); + printf(" --modversion print the specified module's version to stdout\n"); + printf(" --internal-cflags do not filter 'internal' cflags from output\n"); + printf(" --license print the specified module's license to stdout if known\n"); + printf(" --exists-cflags add -DHAVE_FOO fragments to cflags for each found module\n"); + + printf("\nfiltering output:\n\n"); +#ifndef PKGCONF_LITE + printf(" --msvc-syntax print translatable fragments in MSVC syntax\n"); +#endif + printf(" --fragment-filter=types filter output fragments to the specified types\n"); + printf(" --env=prefix print output as shell-compatible environmental variables\n"); + printf(" --fragment-tree visualize printed CFLAGS/LIBS fragments as a tree\n"); + + printf("\nreport bugs to <%s>.\n", PACKAGE_BUGREPORT); +} + +static void +relocate_path(const char *path) +{ + char buf[PKGCONF_BUFSIZE]; + + pkgconf_strlcpy(buf, path, sizeof buf); + pkgconf_path_relocate(buf, sizeof buf); + + printf("%s\n", buf); +} + +#ifndef PKGCONF_LITE +static void +dump_personality(const pkgconf_cross_personality_t *p) +{ + pkgconf_node_t *n; + + printf("Triplet: %s\n", p->name); + + if (p->sysroot_dir) + printf("SysrootDir: %s\n", p->sysroot_dir); + + printf("DefaultSearchPaths: "); + PKGCONF_FOREACH_LIST_ENTRY(p->dir_list.head, n) + { + pkgconf_path_t *pn = n->data; + printf("%s ", pn->path); + } + + printf("\n"); + printf("SystemIncludePaths: "); + PKGCONF_FOREACH_LIST_ENTRY(p->filter_includedirs.head, n) + { + pkgconf_path_t *pn = n->data; + printf("%s ", pn->path); + } + + printf("\n"); + printf("SystemLibraryPaths: "); + PKGCONF_FOREACH_LIST_ENTRY(p->filter_libdirs.head, n) + { + pkgconf_path_t *pn = n->data; + printf("%s ", pn->path); + } + + printf("\n"); +} + +static pkgconf_cross_personality_t * +deduce_personality(char *argv[]) +{ + const char *argv0 = argv[0]; + char *i, *prefix; + pkgconf_cross_personality_t *out; + + i = strrchr(argv0, '/'); + if (i != NULL) + argv0 = i + 1; + +#if defined(_WIN32) || defined(_WIN64) + i = strrchr(argv0, '\\'); + if (i != NULL) + argv0 = i + 1; +#endif + + i = strstr(argv0, "-pkg"); + if (i == NULL) + return pkgconf_cross_personality_default(); + + prefix = pkgconf_strndup(argv0, i - argv0); + out = pkgconf_cross_personality_find(prefix); + free(prefix); + if (out == NULL) + return pkgconf_cross_personality_default(); + + return out; +} +#endif + +static void +unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) +{ + (void) client; + + if (pkgconf_unveil(path, permissions) == -1) + { + fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static bool +unveil_search_paths(const pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_node_t *n; + + if (pkgconf_unveil("/dev/null", "rwc") == -1) + return false; + + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) + { + pkgconf_path_t *pn = n->data; + + if (pkgconf_unveil(pn->path, "r") == -1) + return false; + } + + PKGCONF_FOREACH_LIST_ENTRY(personality->dir_list.head, n) + { + pkgconf_path_t *pn = n->data; + + if (pkgconf_unveil(pn->path, "r") == -1) + return false; + } + + pkgconf_client_set_unveil_handler(&pkg_client, unveil_handler); + + return true; +} + +int +main(int argc, char *argv[]) +{ + int ret; + pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER; + pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER; + char *builddir; + char *sysroot_dir; + char *env_traverse_depth; + char *required_pkgconfig_version = NULL; + char *required_exact_module_version = NULL; + char *required_max_module_version = NULL; + char *required_module_version = NULL; + char *logfile_arg = NULL; + char *want_env_prefix = NULL; + unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE; + pkgconf_cross_personality_t *personality = NULL; + bool opened_error_msgout = false; + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + if (pkgconf_pledge("stdio rpath wpath cpath unveil", NULL) == -1) + { + fprintf(stderr, "pkgconf: pledge failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + want_flags = 0; + +#ifdef _WIN32 + /* When running regression tests in cygwin, and building native + * executable, tests fail unless native executable outputs unix + * line endings. Come to think of it, this will probably help + * real people who use cygwin build environments but native pkgconf, too. + */ + _setmode(fileno(stdout), O_BINARY); + _setmode(fileno(stderr), O_BINARY); +#endif + + struct pkg_option options[] = { + { "version", no_argument, &want_flags, PKG_VERSION|PKG_PRINT_ERRORS, }, + { "about", no_argument, &want_flags, PKG_ABOUT|PKG_PRINT_ERRORS, }, + { "atleast-version", required_argument, NULL, 2, }, + { "atleast-pkgconfig-version", required_argument, NULL, 3, }, + { "libs", no_argument, &want_flags, PKG_LIBS|PKG_PRINT_ERRORS, }, + { "cflags", no_argument, &want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, }, + { "modversion", no_argument, &want_flags, PKG_MODVERSION|PKG_PRINT_ERRORS, }, + { "variable", required_argument, NULL, 7, }, + { "exists", no_argument, &want_flags, PKG_EXISTS, }, + { "print-errors", no_argument, &want_flags, PKG_PRINT_ERRORS, }, + { "short-errors", no_argument, &want_flags, PKG_SHORT_ERRORS, }, + { "maximum-traverse-depth", required_argument, NULL, 11, }, + { "static", no_argument, &want_flags, PKG_STATIC, }, + { "shared", no_argument, &want_flags, PKG_SHARED, }, + { "pure", no_argument, &want_flags, PKG_PURE, }, + { "print-requires", no_argument, &want_flags, PKG_REQUIRES, }, + { "print-variables", no_argument, &want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, }, +#ifndef PKGCONF_LITE + { "digraph", no_argument, &want_flags, PKG_DIGRAPH, }, + { "solution", no_argument, &want_flags, PKG_SOLUTION, }, +#endif + { "help", no_argument, &want_flags, PKG_HELP, }, + { "env-only", no_argument, &want_flags, PKG_ENV_ONLY, }, + { "print-requires-private", no_argument, &want_flags, PKG_REQUIRES_PRIVATE, }, + { "cflags-only-I", no_argument, &want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, }, + { "cflags-only-other", no_argument, &want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "libs-only-L", no_argument, &want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, }, + { "libs-only-l", no_argument, &want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, }, + { "libs-only-other", no_argument, &want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, }, + { "uninstalled", no_argument, &want_flags, PKG_UNINSTALLED, }, + { "no-uninstalled", no_argument, &want_flags, PKG_NO_UNINSTALLED, }, + { "keep-system-cflags", no_argument, &want_flags, PKG_KEEP_SYSTEM_CFLAGS, }, + { "keep-system-libs", no_argument, &want_flags, PKG_KEEP_SYSTEM_LIBS, }, + { "define-variable", required_argument, NULL, 27, }, + { "exact-version", required_argument, NULL, 28, }, + { "max-version", required_argument, NULL, 29, }, + { "ignore-conflicts", no_argument, &want_flags, PKG_IGNORE_CONFLICTS, }, + { "errors-to-stdout", no_argument, &want_flags, PKG_ERRORS_ON_STDOUT, }, + { "silence-errors", no_argument, &want_flags, PKG_SILENCE_ERRORS, }, + { "list-all", no_argument, &want_flags, PKG_LIST|PKG_PRINT_ERRORS, }, + { "list-package-names", no_argument, &want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, }, +#ifndef PKGCONF_LITE + { "simulate", no_argument, &want_flags, PKG_SIMULATE, }, +#endif + { "no-cache", no_argument, &want_flags, PKG_NO_CACHE, }, + { "print-provides", no_argument, &want_flags, PKG_PROVIDES, }, + { "no-provides", no_argument, &want_flags, PKG_NO_PROVIDES, }, + { "debug", no_argument, &want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, }, + { "validate", no_argument, &want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT }, + { "log-file", required_argument, NULL, 40 }, + { "path", no_argument, &want_flags, PKG_PATH }, + { "with-path", required_argument, NULL, 42 }, + { "prefix-variable", required_argument, NULL, 43 }, + { "define-prefix", no_argument, &want_flags, PKG_DEFINE_PREFIX }, + { "relocate", required_argument, NULL, 45 }, + { "dont-define-prefix", no_argument, &want_flags, PKG_DONT_DEFINE_PREFIX }, + { "dont-relocate-paths", no_argument, &want_flags, PKG_DONT_RELOCATE_PATHS }, + { "env", required_argument, NULL, 48 }, +#ifndef PKGCONF_LITE + { "msvc-syntax", no_argument, &want_flags, PKG_MSVC_SYNTAX }, +#endif + { "fragment-filter", required_argument, NULL, 50 }, + { "internal-cflags", no_argument, &want_flags, PKG_INTERNAL_CFLAGS }, +#ifndef PKGCONF_LITE + { "dump-personality", no_argument, &want_flags, PKG_DUMP_PERSONALITY }, + { "personality", required_argument, NULL, 53 }, +#endif + { "license", no_argument, &want_flags, PKG_DUMP_LICENSE }, + { "verbose", no_argument, NULL, 55 }, + { "exists-cflags", no_argument, &want_flags, PKG_EXISTS_CFLAGS }, + { "fragment-tree", no_argument, &want_flags, PKG_FRAGMENT_TREE }, + { NULL, 0, NULL, 0 } + }; + +#ifndef PKGCONF_LITE + if (getenv("PKG_CONFIG_EARLY_TRACE")) + { + error_msgout = stderr; + pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); + } +#endif + + while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1) + { + switch (ret) + { + case 2: + required_module_version = pkg_optarg; + break; + case 3: + required_pkgconfig_version = pkg_optarg; + break; + case 7: + want_variable = pkg_optarg; + break; + case 11: + maximum_traverse_depth = atoi(pkg_optarg); + break; + case 27: + pkgconf_tuple_define_global(&pkg_client, pkg_optarg); + break; + case 28: + required_exact_module_version = pkg_optarg; + break; + case 29: + required_max_module_version = pkg_optarg; + break; + case 40: + logfile_arg = pkg_optarg; + break; + case 42: + pkgconf_path_prepend(pkg_optarg, &dir_list, true); + break; + case 43: + pkgconf_client_set_prefix_varname(&pkg_client, pkg_optarg); + break; + case 45: + relocate_path(pkg_optarg); + return EXIT_SUCCESS; + case 48: + want_env_prefix = pkg_optarg; + break; + case 50: + want_fragment_filter = pkg_optarg; + break; +#ifndef PKGCONF_LITE + case 53: + personality = pkgconf_cross_personality_find(pkg_optarg); + break; +#endif + case 55: + verbosity++; + break; + case '?': + case ':': + ret = EXIT_FAILURE; + goto out; + default: + break; + } + } + + if (personality == NULL) { +#ifndef PKGCONF_LITE + personality = deduce_personality(argv); +#else + personality = pkgconf_cross_personality_default(); +#endif + } + +#ifndef PKGCONF_LITE + if ((want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY) + { + dump_personality(personality); + return EXIT_SUCCESS; + } +#endif + + /* now, bring up the client. settings are preserved since the client is prealloced */ + pkgconf_client_init(&pkg_client, error_handler, NULL, personality); + + /* unveil the entire search path now that we have loaded the personality data. */ + if (!unveil_search_paths(&pkg_client, personality)) + { + fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + +#ifndef PKGCONF_LITE + if ((want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX || getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL) + want_render_ops = msvc_renderer_get(); +#endif + + if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL) + maximum_traverse_depth = atoi(env_traverse_depth); + + if ((want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS) + want_flags |= (PKG_SILENCE_ERRORS); + + if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW")) + want_flags |= (PKG_SILENCE_ERRORS); + else + want_flags &= ~(PKG_SILENCE_ERRORS); + + if (getenv("PKG_CONFIG_DONT_RELOCATE_PATHS")) + want_flags |= (PKG_DONT_RELOCATE_PATHS); + + if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE || (want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_warn_handler(&pkg_client, error_handler, NULL); + +#ifndef PKGCONF_LITE + if ((want_flags & PKG_DEBUG) == PKG_DEBUG) + pkgconf_client_set_trace_handler(&pkg_client, error_handler, NULL); +#endif + + pkgconf_path_prepend_list(&pkg_client.dir_list, &dir_list); + pkgconf_path_free(&dir_list); + + if ((want_flags & PKG_ABOUT) == PKG_ABOUT) + { + about(); + + ret = EXIT_SUCCESS; + goto out; + } + + if ((want_flags & PKG_VERSION) == PKG_VERSION) + { + version(); + + ret = EXIT_SUCCESS; + goto out; + } + + if ((want_flags & PKG_HELP) == PKG_HELP) + { + usage(); + + ret = EXIT_SUCCESS; + goto out; + } + + if (getenv("PKG_CONFIG_FDO_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; + + if (getenv("PKG_CONFIG_PKGCONF1_SYSROOT_RULES")) + want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES; + + if ((want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS) + want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS; + + if ((want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS; + + error_msgout = stderr; + if ((want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT) + error_msgout = stdout; + if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) { + error_msgout = fopen(PATH_DEV_NULL, "w"); + opened_error_msgout = true; + } + + if ((want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || getenv("PKG_CONFIG_IGNORE_CONFLICTS") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; + + if ((want_flags & PKG_STATIC) == PKG_STATIC || personality->want_default_static) + want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + if ((want_flags & PKG_SHARED) == PKG_SHARED) + want_client_flags &= ~(PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS); + + /* if --static and --pure are both specified, then disable merge-back. + * this allows for a --static which searches private modules, but has the same fragment behaviour as if + * --static were disabled. see for rationale. + */ + if ((want_flags & PKG_PURE) == PKG_PURE || getenv("PKG_CONFIG_PURE_DEPGRAPH") != NULL || personality->want_default_pure) + want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS; + + if ((want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY) + want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY; + + if ((want_flags & PKG_NO_CACHE) == PKG_NO_CACHE) + want_client_flags |= PKGCONF_PKG_PKGF_NO_CACHE; + +/* On Windows we want to always redefine the prefix by default + * but allow that behavior to be manually disabled */ +#if !defined(_WIN32) && !defined(_WIN64) + if ((want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || getenv("PKG_CONFIG_RELOCATE_PATHS") != NULL) +#endif + want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || getenv("PKG_CONFIG_DISABLE_UNINSTALLED") != NULL) + want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED; + + if ((want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES) + want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES; + + if ((want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX || getenv("PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL) + want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX; + + if ((want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS) + want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS; + + /* if these selectors are used, it means that we are querying metadata. + * so signal to libpkgconf that we only want to walk the flattened dependency set. + */ + if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION || + (want_flags & PKG_REQUIRES) == PKG_REQUIRES || + (want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (want_flags & PKG_PROVIDES) == PKG_PROVIDES || + (want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (want_flags & PKG_PATH) == PKG_PATH || + want_variable != NULL) + maximum_traverse_depth = 1; + + /* if we are asking for a variable, path or list of variables, this only makes sense + * for a single package. + */ + if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES || + (want_flags & PKG_PATH) == PKG_PATH || + want_variable != NULL) + maximum_package_count = 1; + + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL) + want_flags |= PKG_KEEP_SYSTEM_CFLAGS; + + if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL) + want_flags |= PKG_KEEP_SYSTEM_LIBS; + + if ((builddir = getenv("PKG_CONFIG_TOP_BUILD_DIR")) != NULL) + pkgconf_client_set_buildroot_dir(&pkg_client, builddir); + + if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE || + (want_flags & PKG_CFLAGS)) + { + want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + } + + if ((sysroot_dir = getenv("PKG_CONFIG_SYSROOT_DIR")) != NULL) + { + const char *destdir; + + pkgconf_client_set_sysroot_dir(&pkg_client, sysroot_dir); + + if ((destdir = getenv("DESTDIR")) != NULL) + { + if (!strcmp(destdir, sysroot_dir)) + want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES; + } + } + + /* we have determined what features we want most likely. in some cases, we override later. */ + pkgconf_client_set_flags(&pkg_client, want_client_flags); + + /* at this point, want_client_flags should be set, so build the dir list */ + pkgconf_client_dir_list_build(&pkg_client, personality); + + /* preload any files in PKG_CONFIG_PRELOADED_FILES */ + pkgconf_client_preload_from_environ(&pkg_client, "PKG_CONFIG_PRELOADED_FILES"); + + if (required_pkgconfig_version != NULL) + { + if (pkgconf_compare_version(PACKAGE_VERSION, required_pkgconfig_version) >= 0) + ret = EXIT_SUCCESS; + else + ret = EXIT_FAILURE; + + goto out; + } + + if ((want_flags & PKG_LIST) == PKG_LIST) + { + pkgconf_scan_all(&pkg_client, NULL, print_list_entry); + ret = EXIT_SUCCESS; + goto out; + } + + if ((want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES) + { + pkgconf_scan_all(&pkg_client, NULL, print_package_entry); + ret = EXIT_SUCCESS; + goto out; + } + + if (logfile_arg == NULL) + logfile_arg = getenv("PKG_CONFIG_LOG"); + + if (logfile_arg != NULL) + { + if (pkgconf_unveil(logfile_arg, "rwc") == -1) + { + fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + + logfile_out = fopen(logfile_arg, "w"); + pkgconf_audit_set_log(&pkg_client, logfile_out); + } + + if (required_module_version != NULL) + { + pkgconf_pkg_t *pkg = NULL; + pkgconf_node_t *node; + pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; + + while (argv[pkg_optind]) + { + pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); + pkg_optind++; + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *pkgiter = node->data; + + pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); + if (pkg == NULL) + { + if (want_flags & PKG_PRINT_ERRORS) + pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); + + ret = EXIT_FAILURE; + goto cleanup; + } + + if (pkgconf_compare_version(pkg->version, required_module_version) >= 0) + { + ret = EXIT_SUCCESS; + goto cleanup; + } + } + + ret = EXIT_FAILURE; +cleanup: + if (pkg != NULL) + pkgconf_pkg_unref(&pkg_client, pkg); + pkgconf_dependency_free(&deplist); + goto out; + } + else if (required_exact_module_version != NULL) + { + pkgconf_pkg_t *pkg = NULL; + pkgconf_node_t *node; + pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; + + while (argv[pkg_optind]) + { + pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); + pkg_optind++; + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *pkgiter = node->data; + + pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); + if (pkg == NULL) + { + if (want_flags & PKG_PRINT_ERRORS) + pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); + + ret = EXIT_FAILURE; + goto cleanup2; + } + + if (pkgconf_compare_version(pkg->version, required_exact_module_version) == 0) + { + ret = EXIT_SUCCESS; + goto cleanup2; + } + } + + ret = EXIT_FAILURE; +cleanup2: + if (pkg != NULL) + pkgconf_pkg_unref(&pkg_client, pkg); + pkgconf_dependency_free(&deplist); + goto out; + } + else if (required_max_module_version != NULL) + { + pkgconf_pkg_t *pkg = NULL; + pkgconf_node_t *node; + pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER; + + while (argv[pkg_optind]) + { + pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0); + pkg_optind++; + } + + PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node) + { + pkgconf_dependency_t *pkgiter = node->data; + + pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package); + if (pkg == NULL) + { + if (want_flags & PKG_PRINT_ERRORS) + pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package); + + ret = EXIT_FAILURE; + goto cleanup3; + } + + if (pkgconf_compare_version(pkg->version, required_max_module_version) <= 0) + { + ret = EXIT_SUCCESS; + goto cleanup3; + } + } + + ret = EXIT_FAILURE; +cleanup3: + if (pkg != NULL) + pkgconf_pkg_unref(&pkg_client, pkg); + pkgconf_dependency_free(&deplist); + goto out; + } + + while (1) + { + char *package = argv[pkg_optind]; + char *end; + + if (package == NULL) + break; + + /* check if there is a limit to the number of packages allowed to be included, if so and we have hit + * the limit, stop adding packages to the queue. + */ + if (maximum_package_count > 0 && pkgq.length >= maximum_package_count) + break; + + while (isspace((unsigned char)package[0])) + package++; + + /* skip empty packages */ + if (package[0] == '\0') { + pkg_optind++; + continue; + } + + end = package + strlen(package) - 1; + while(end > package && isspace((unsigned char)end[0])) end--; + end[1] = '\0'; + + if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1]))) + { + pkgconf_queue_push(&pkgq, package); + pkg_optind++; + } + else if (argv[pkg_optind + 2] == NULL) + { + char packagebuf[PKGCONF_BUFSIZE]; + + snprintf(packagebuf, sizeof packagebuf, "%s %s", package, argv[pkg_optind + 1]); + pkg_optind += 2; + + pkgconf_queue_push(&pkgq, packagebuf); + } + else + { + char packagebuf[PKGCONF_BUFSIZE]; + + snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]); + pkg_optind += 3; + + pkgconf_queue_push(&pkgq, packagebuf); + } + } + + if (pkgq.head == NULL) + { + fprintf(stderr, "Please specify at least one package name on the command line.\n"); + ret = EXIT_FAILURE; + goto out; + } + + ret = EXIT_SUCCESS; + + if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth)) + { + ret = EXIT_FAILURE; + goto out; + } + + /* we shouldn't need to unveil any more filesystem accesses from this point, so lock it down */ + if (pkgconf_unveil(NULL, NULL) == -1) + { + fprintf(stderr, "pkgconf: unveil lockdown failed: %s\n", strerror(errno)); + return EXIT_FAILURE; + } + +#ifndef PKGCONF_LITE + if ((want_flags & PKG_SIMULATE) == PKG_SIMULATE) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS); + apply_simulate(&pkg_client, &world, NULL, -1); + } +#endif + + if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE) + goto out; + + if ((want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE) + { + apply_license(&pkg_client, &world, &ret, 2); + goto out; + } + + if ((want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED) + { + ret = EXIT_FAILURE; + apply_uninstalled(&pkg_client, &world, &ret, 2); + goto out; + } + + if (want_env_prefix != NULL) + { + apply_env(&pkg_client, &world, want_env_prefix, 2); + goto out; + } + + if ((want_flags & PKG_PROVIDES) == PKG_PROVIDES) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_provides(&pkg_client, &world, NULL, 2); + } + +#ifndef PKGCONF_LITE + if ((want_flags & PKG_DIGRAPH) == PKG_DIGRAPH) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_digraph(&pkg_client, &world, &pkgq, 2); + } + + if ((want_flags & PKG_SOLUTION) == PKG_SOLUTION) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_print_solution(&pkg_client, &world, NULL, 2); + } +#endif + + if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_modversion(&pkg_client, &world, &pkgq, 2); + } + + if ((want_flags & PKG_PATH) == PKG_PATH) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_path(&pkg_client, &world, NULL, 2); + } + + if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_variables(&pkg_client, &world, NULL, 2); + } + + if (want_variable) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL); + apply_variable(&pkg_client, &world, want_variable, 2); + } + + if ((want_flags & PKG_REQUIRES) == PKG_REQUIRES) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + apply_requires(&pkg_client, &world, NULL, 2); + } + + if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_requires_private(&pkg_client, &world, NULL, 2); + } + + if ((want_flags & PKG_FRAGMENT_TREE)) + { + want_flags &= ~(PKG_CFLAGS|PKG_LIBS); + + apply_fragment_tree(&pkg_client, &world, NULL, 2); + } + + if ((want_flags & PKG_CFLAGS)) + { + apply_cflags(&pkg_client, &world, NULL, 2); + } + + if ((want_flags & PKG_LIBS)) + { + if (want_flags & PKG_CFLAGS) + printf(" "); + + if (!(want_flags & PKG_STATIC)) + pkgconf_client_set_flags(&pkg_client, pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE); + + apply_libs(&pkg_client, &world, NULL, 2); + } + + if (want_flags & (PKG_CFLAGS|PKG_LIBS)) + printf("\n"); + +out: + pkgconf_solution_free(&pkg_client, &world); + pkgconf_queue_free(&pkgq); + pkgconf_cross_personality_deinit(personality); + pkgconf_client_deinit(&pkg_client); + + if (logfile_out != NULL) + fclose(logfile_out); + if (opened_error_msgout) + fclose(error_msgout); + + return ret; +} diff --git a/cli/renderer-msvc.c b/cli/renderer-msvc.c new file mode 100644 index 00000000000..8e31699bbca --- /dev/null +++ b/cli/renderer-msvc.c @@ -0,0 +1,172 @@ +/* + * renderer-msvc.c + * MSVC library syntax renderer + * + * Copyright (c) 2017 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +#include +#include "renderer-msvc.h" + +static inline bool +fragment_should_quote(const pkgconf_fragment_t *frag) +{ + const char *src; + + if (frag->data == NULL) + return false; + + for (src = frag->data; *src; src++) + { + if (((*src < ' ') || + (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') || + (*src > '$' && *src < '(') || + (*src > ')' && *src < '+') || + (*src > ':' && *src < '=') || + (*src > '=' && *src < '@') || + (*src > 'Z' && *src < '^') || + (*src == '`') || + (*src > 'z' && *src < '~') || + (*src > '~'))) + return true; + } + + return false; +} + +static inline size_t +fragment_len(const pkgconf_fragment_t *frag) +{ + size_t len = 1; + + if (frag->type) + len += 2; + + if (frag->data != NULL) + { + len += strlen(frag->data); + + if (fragment_should_quote(frag)) + len += 2; + } + + return len; +} + +static inline bool +allowed_fragment(const pkgconf_fragment_t *frag) +{ + return !(!frag->type || frag->data == NULL || strchr("DILl", frag->type) == NULL); +} + +static size_t +msvc_renderer_render_len(const pkgconf_list_t *list, bool escape) +{ + (void) escape; + + size_t out = 1; /* trailing nul */ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + const pkgconf_fragment_t *frag = node->data; + + if (!allowed_fragment(frag)) + continue; + + switch (frag->type) + { + case 'L': + out += 9; /* "/libpath:" */ + break; + case 'l': + out += 4; /* ".lib" */ + break; + default: + break; + } + + out += fragment_len(frag); + } + + return out; +} + +static void +msvc_renderer_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape) +{ + pkgconf_node_t *node; + char *bptr = buf; + + memset(buf, 0, buflen); + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + const pkgconf_fragment_t *frag = node->data; + size_t buf_remaining = buflen - (bptr - buf); + size_t cnt; + + if (!allowed_fragment(frag)) + continue; + + if (fragment_len(frag) > buf_remaining) + break; + + switch(frag->type) { + case 'D': + case 'I': + *bptr++ = '/'; + *bptr++ = frag->type; + break; + case 'L': + cnt = pkgconf_strlcpy(bptr, "/libpath:", buf_remaining); + bptr += cnt; + buf_remaining -= cnt; + break; + } + + escape = fragment_should_quote(frag); + + if (escape) + *bptr++ = '"'; + + cnt = pkgconf_strlcpy(bptr, frag->data, buf_remaining); + bptr += cnt; + buf_remaining -= cnt; + + if (frag->type == 'l') + { + cnt = pkgconf_strlcpy(bptr, ".lib", buf_remaining); + bptr += cnt; + } + + if (escape) + *bptr++ = '"'; + + *bptr++ = ' '; + } + + *bptr = '\0'; +} + +static const pkgconf_fragment_render_ops_t msvc_renderer_ops = { + .render_len = msvc_renderer_render_len, + .render_buf = msvc_renderer_render_buf +}; + +const pkgconf_fragment_render_ops_t * +msvc_renderer_get(void) +{ + return &msvc_renderer_ops; +} diff --git a/cli/renderer-msvc.h b/cli/renderer-msvc.h new file mode 100644 index 00000000000..2a095fdc36b --- /dev/null +++ b/cli/renderer-msvc.h @@ -0,0 +1,23 @@ +/* + * renderer-msvc.h + * MSVC library syntax renderer header + * + * Copyright (c) 2017 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef RENDERER_MSVC_H +#define RENDERER_MSVC_H + +#include + +const pkgconf_fragment_render_ops_t *msvc_renderer_get(void); + +#endif diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000000..3fee9bd444e --- /dev/null +++ b/configure.ac @@ -0,0 +1,66 @@ +dnl configure.ac +dnl m4 preprocessor script for autotools +dnl +dnl Copyright (c) 2011, 2012, 2013, 2014 pkgconf authors (see AUTHORS). +dnl +dnl Permission to use, copy, modify, and/or distribute this software for any +dnl purpose with or without fee is hereby granted, provided that the above +dnl copyright notice and this permission notice appear in all copies. +dnl +dnl This software is provided 'as is' and without any warranty, express or +dnl implied. In no event shall the authors be liable for any damages arising +dnl from the use of this software. + +AC_PREREQ([2.71]) +AC_INIT([pkgconf],[2.5.1],[https://github.com/pkgconf/pkgconf/issues/new]) +AC_CONFIG_SRCDIR([cli/main.c]) +AC_CONFIG_MACRO_DIR([m4]) +AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"]) +AX_CHECK_COMPILE_FLAG([-Wextra], [CFLAGS="$CFLAGS -Wextra"]) +AX_CHECK_COMPILE_FLAG([-Wformat=2], [CFLAGS="$CFLAGS -Wformat=2"]) +AX_CHECK_COMPILE_FLAG([-std=gnu99], [CFLAGS="$CFLAGS -std=gnu99"], [ + AX_CHECK_COMPILE_FLAG([-std=c99], [CFLAGS="$CFLAGS -std=c99"]) +]) +AC_CONFIG_HEADERS([libpkgconf/config.h]) +AC_CHECK_DECLS([strlcpy, strlcat, strndup], [], [], [[#include ]]) +AC_CHECK_DECLS([pledge, unveil], [], [], [[#include ]]) +AC_CHECK_DECLS([reallocarray]) +AC_CHECK_HEADERS([sys/stat.h]) +AM_INIT_AUTOMAKE([foreign dist-xz subdir-objects]) +AM_SILENT_RULES([yes]) +LT_INIT + +AC_SYS_LARGEFILE + +AC_ARG_WITH([personality-dir],[AS_HELP_STRING([--with-personality-dir],[specify + the place where cross-compile personality files will be found])], + PERSONALITY_PATH="$withval", + PERSONALITY_PATH="${datadir}/pkgconfig/personality.d:${sysconfdir}/pkgconfig/personality.d") + +AC_SUBST([PERSONALITY_PATH]) + +AC_ARG_WITH([pkg-config-dir],[AS_HELP_STRING([--with-pkg-config-dir],[specify + the place where pc files will be found])],PKG_DEFAULT_PATH="$withval", + PKG_DEFAULT_PATH="${libdir}/pkgconfig:${datadir}/pkgconfig") + +AC_SUBST([PKG_DEFAULT_PATH]) + +AC_ARG_WITH([system-libdir],[AS_HELP_STRING([--with-system-libdir],[specify the + system library directory (default LIBDIR)])], + SYSTEM_LIBDIR="$withval", SYSTEM_LIBDIR="${libdir}") + +AC_SUBST([SYSTEM_LIBDIR]) + +AC_ARG_WITH([system-includedir],[AS_HELP_STRING([--with-system-includedir],[specify the + system include directory (default INCLUDEDIR)])], + SYSTEM_INCLUDEDIR="$withval", SYSTEM_INCLUDEDIR="${includedir}") + +AC_SUBST([SYSTEM_INCLUDEDIR]) + +AC_PROG_CPP +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_LN_S + +AC_CONFIG_FILES([Makefile Kyuafile libpkgconf.pc tests/Kyuafile tests/test_env.sh]) +AC_OUTPUT diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000000..ca608c7f106 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# pkgconf documentation build configuration file, created by +# sphinx-quickstart on Sat Dec 10 16:54:40 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'pkgconf' +copyright = '2016, pkgconf authors' +author = 'pkgconf authors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.1.0' +# The full version, including alpha/beta/rc tags. +release = '1.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = 'pkgconf v1.1.0' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pkgconfdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pkgconf.tex', 'pkgconf Documentation', + 'pkgconf authors', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pkgconf', 'pkgconf Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pkgconf', 'pkgconf Documentation', + author, 'pkgconf', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/doc/extract.py b/doc/extract.py new file mode 100644 index 00000000000..10ea6203786 --- /dev/null +++ b/doc/extract.py @@ -0,0 +1,149 @@ +# derived from https://github.com/jeanralphaviles/comment_parser/blob/master/comment_parser/parsers/c_parser.py +# MIT license - https://github.com/jeanralphaviles/comment_parser/blob/master/LICENSE + + +class Comment: + def __init__(self, comment, line, multiline): + self.comment = comment + self.line = line + self.multiline = multiline + + def __repr__(self): + return "Comment(comment=%r, line=%r, multiline=%r)" % (self.comment, self.line, self.multiline) + + @property + def clean_text(self): + if not self.multiline: + return self.comment.strip() + + lines = self.comment.splitlines() + cleanlines = [] + for line in lines: + if line[0:3] == ' * ': + cleanlines.append(line[3:]) + elif len(line) == 2: + cleanlines.append('') + return '\n'.join(cleanlines) + + @property + def doc_text(self): + text = self.clean_text + if '!doc' in text[0:4]: + return text[5:] + return None + + +class FileError(Exception): + pass + + +class UnterminatedCommentError(Exception): + pass + + +def extract_comments(filename): + """Extracts a list of comments from the given C family source file. + Comments are represented with the Comment class found in the common module. + C family comments come in two forms, single and multi-line comments. + - Single-line comments begin with '//' and continue to the end of line. + - Multi-line comments begin with '/*' and end with '*/' and can span + multiple lines of code. If a multi-line comment does not terminate + before EOF is reached, then an exception is raised. + Note that this doesn't take language-specific preprocessor directives into + consideration. + Args: + filename: String name of the file to extract comments from. + Returns: + Python list of Comment objects in the order that they appear in the file. + Raises: + FileError: File was unable to be open or read. + UnterminatedCommentError: Encountered an unterminated multi-line + comment. + """ + try: + with open(filename, 'r') as source_file: + state = 0 + current_comment = '' + comments = [] + line_counter = 1 + comment_start = 1 + while True: + char = source_file.read(1) + if not char: + if state == 3 or state == 4: + raise UnterminatedCommentError() + if state == 2: + # Was in single line comment. Create comment. + comment = Comment(current_comment, line_counter, False) + comments.append(comment) + return comments + if state == 0: + # Waiting for comment start character or beginning of + # string. + if char == '/': + state = 1 + elif char == '"': + state = 5 + elif state == 1: + # Found comment start character, classify next character and + # determine if single or multiline comment. + if char == '/': + state = 2 + elif char == '*': + comment_start = line_counter + state = 3 + else: + state = 0 + elif state == 2: + # In single line comment, read characters until EOL. + if char == '\n': + comment = Comment(current_comment, line_counter, False) + comments.append(comment) + current_comment = '' + state = 0 + else: + current_comment += char + elif state == 3: + # In multi-line comment, add characters until '*' + # encountered. + if char == '*': + state = 4 + else: + current_comment += char + elif state == 4: + # In multi-line comment with asterisk found. Determine if + # comment is ending. + if char == '/': + comment = Comment( + current_comment, comment_start, True) + comments.append(comment) + current_comment = '' + state = 0 + else: + current_comment += '*' + # Care for multiple '*' in a row + if char != '*': + current_comment += char + state = 3 + elif state == 5: + # In string literal, expect literal end or escape char. + if char == '"': + state = 0 + elif char == '\\': + state = 6 + elif state == 6: + # In string literal, escaping current char. + state = 5 + if char == '\n': + line_counter += 1 + except OSError as exception: + raise FileError(str(exception)) + + +if __name__ == '__main__': + import sys + from pprint import pprint + + comments = [comment for comment in extract_comments(sys.argv[1]) if comment.doc_text] + for comment in comments: + print(comment.doc_text) diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000000..b0c7c4bd24b --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,14 @@ +pkgconf +======= + +.. toctree:: + :maxdepth: 2 + + libpkgconf + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/doc/libpkgconf-argvsplit.rst b/doc/libpkgconf-argvsplit.rst new file mode 100644 index 00000000000..94ec6d93f85 --- /dev/null +++ b/doc/libpkgconf-argvsplit.rst @@ -0,0 +1,23 @@ + +libpkgconf `argvsplit` module +============================= + +This is a lowlevel module which provides parsing of strings into argument vectors, +similar to what a shell would do. + +.. c:function:: void pkgconf_argv_free(char **argv) + + Frees an argument vector. + + :param char** argv: The argument vector to free. + :return: nothing + +.. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv) + + Splits a string into an argument vector. + + :param char* src: The string to split. + :param int* argc: A pointer to an integer to store the argument count. + :param char*** argv: A pointer to a pointer for an argument vector. + :return: 0 on success, -1 on error. + :rtype: int diff --git a/doc/libpkgconf-audit.rst b/doc/libpkgconf-audit.rst new file mode 100644 index 00000000000..0baaba30818 --- /dev/null +++ b/doc/libpkgconf-audit.rst @@ -0,0 +1,35 @@ + +libpkgconf `audit` module +========================= + +The libpkgconf `audit` module contains the functions related to attaching an audit log file +to a ``pkgconf_client_t`` object. + +The audit log format is the same as the output generated by the ``PKG_CONFIG_LOG`` environment +variable. + +.. c:function:: void pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf) + + Sets the audit log file pointer on `client` to `auditf`. + The callee is responsible for closing any previous log files. + + :param pkgconf_client_t* client: The client object to modify. + :param FILE* auditf: The file pointer for the already open log file. + :return: nothing + +.. c:function:: void pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...) + + Logs a message to the opened audit log (if any). + + :param pkgconf_client_t* client: The client object the log message is for. + :param char* format: The format string to use for the log messages. + :return: nothing + +.. c:function:: void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode) + + Convenience function which logs a dependency node to the opened audit log (if any). + + :param pkgconf_client_t* client: The client object the log message is for. + :param pkgconf_pkg_t* dep: The dependency package object being logged. + :param pkgconf_dependency_t* depnode: The dependency object itself being logged. + :return: nothing diff --git a/doc/libpkgconf-cache.rst b/doc/libpkgconf-cache.rst new file mode 100644 index 00000000000..adfa79d7958 --- /dev/null +++ b/doc/libpkgconf-cache.rst @@ -0,0 +1,45 @@ + +libpkgconf `cache` module +========================= + +The libpkgconf `cache` module manages a package/module object cache, allowing it to +avoid loading duplicate copies of a package/module. + +A cache is tied to a specific pkgconf client object, so package objects should not +be shared across threads. + +.. c:function:: pkgconf_pkg_t *pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id) + + Looks up a package in the cache given an `id` atom, + such as ``gtk+-3.0`` and returns the already loaded version + if present. + + :param pkgconf_client_t* client: The client object to access. + :param char* id: The package atom to look up in the client object's cache. + :return: A package object if present, else ``NULL``. + :rtype: pkgconf_pkg_t * + +.. c:function:: void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + + Adds an entry for the package to the package cache. + The cache entry must be removed if the package is freed. + + :param pkgconf_client_t* client: The client object to modify. + :param pkgconf_pkg_t* pkg: The package object to add to the client object's cache. + :return: nothing + +.. c:function:: void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + + Deletes a package from the client object's package cache. + + :param pkgconf_client_t* client: The client object to modify. + :param pkgconf_pkg_t* pkg: The package object to remove from the client object's cache. + :return: nothing + +.. c:function:: void pkgconf_cache_free(pkgconf_client_t *client) + + Releases all resources related to a client object's package cache. + This function should only be called to clear a client object's package cache, + as it may release any package in the cache. + + :param pkgconf_client_t* client: The client object to modify. diff --git a/doc/libpkgconf-client.rst b/doc/libpkgconf-client.rst new file mode 100644 index 00000000000..6816fdf4b34 --- /dev/null +++ b/doc/libpkgconf-client.rst @@ -0,0 +1,212 @@ + +libpkgconf `client` module +========================== + +The libpkgconf `client` module implements the `pkgconf_client_t` "client" object. +Client objects store all necessary state for libpkgconf allowing for multiple instances to run +in parallel. + +Client objects are not thread safe, in other words, a client object should not be shared across +thread boundaries. + +.. c:function:: void pkgconf_client_dir_list_build(pkgconf_client_t *client) + + Bootstraps the package search paths. If the ``PKGCONF_PKG_PKGF_ENV_ONLY`` `flag` is set on the client, + then only the ``PKG_CONFIG_PATH`` environment variable will be used, otherwise both the + ``PKG_CONFIG_PATH`` and ``PKG_CONFIG_LIBDIR`` environment variables will be used. + + :param pkgconf_client_t* client: The pkgconf client object to bootstrap. + :return: nothing + +.. c:function:: void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) + + Initialise a pkgconf client object. + + :param pkgconf_client_t* client: The client to initialise. + :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. + :param void* error_handler_data: user data passed to optional error handler + :param pkgconf_cross_personality_t* personality: the cross-compile personality to use for defaults + :return: nothing + +.. c:function:: pkgconf_client_t* pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) + + Allocate and initialise a pkgconf client object. + + :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. + :param void* error_handler_data: user data passed to optional error handler + :param pkgconf_cross_personality_t* personality: cross-compile personality to use + :return: A pkgconf client object. + :rtype: pkgconf_client_t* + +.. c:function:: void pkgconf_client_deinit(pkgconf_client_t *client) + + Release resources belonging to a pkgconf client object. + + :param pkgconf_client_t* client: The client to deinitialise. + :return: nothing + +.. c:function:: void pkgconf_client_free(pkgconf_client_t *client) + + Release resources belonging to a pkgconf client object and then free the client object itself. + + :param pkgconf_client_t* client: The client to deinitialise and free. + :return: nothing + +.. c:function:: const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client) + + Retrieves the client's sysroot directory (if any). + + :param pkgconf_client_t* client: The client object being accessed. + :return: A string containing the sysroot directory or NULL. + :rtype: const char * + +.. c:function:: void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir) + + Sets or clears the sysroot directory on a client object. Any previous sysroot directory setting is + automatically released if one was previously set. + + Additionally, the global tuple ``$(pc_sysrootdir)`` is set as appropriate based on the new setting. + + :param pkgconf_client_t* client: The client object being modified. + :param char* sysroot_dir: The sysroot directory to set or NULL to unset. + :return: nothing + +.. c:function:: const char *pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client) + + Retrieves the client's buildroot directory (if any). + + :param pkgconf_client_t* client: The client object being accessed. + :return: A string containing the buildroot directory or NULL. + :rtype: const char * + +.. c:function:: void pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir) + + Sets or clears the buildroot directory on a client object. Any previous buildroot directory setting is + automatically released if one was previously set. + + Additionally, the global tuple ``$(pc_top_builddir)`` is set as appropriate based on the new setting. + + :param pkgconf_client_t* client: The client object being modified. + :param char* buildroot_dir: The buildroot directory to set or NULL to unset. + :return: nothing + +.. c:function:: bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) + + Report an error to a client-registered error handler. + + :param pkgconf_client_t* client: The pkgconf client object to report the error to. + :param char* format: A printf-style format string to use for formatting the error message. + :return: true if the error handler processed the message, else false. + :rtype: bool + +.. c:function:: bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) + + Report an error to a client-registered warn handler. + + :param pkgconf_client_t* client: The pkgconf client object to report the error to. + :param char* format: A printf-style format string to use for formatting the warning message. + :return: true if the warn handler processed the message, else false. + :rtype: bool + +.. c:function:: bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t len, const char *funcname, const char *format, ...) + + Report a message to a client-registered trace handler. + + :param pkgconf_client_t* client: The pkgconf client object to report the trace message to. + :param char* filename: The file the function is in. + :param size_t lineno: The line number currently being executed. + :param char* funcname: The function name to use. + :param char* format: A printf-style format string to use for formatting the trace message. + :return: true if the trace handler processed the message, else false. + :rtype: bool + +.. c:function:: bool pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, const void *data) + + The default pkgconf error handler. + + :param char* msg: The error message to handle. + :param pkgconf_client_t* client: The client object the error originated from. + :param void* data: An opaque pointer to extra data associated with the client for error handling. + :return: true (the function does nothing to process the message) + :rtype: bool + +.. c:function:: unsigned int pkgconf_client_get_flags(const pkgconf_client_t *client) + + Retrieves resolver-specific flags associated with a client object. + + :param pkgconf_client_t* client: The client object to retrieve the resolver-specific flags from. + :return: a bitfield of resolver-specific flags + :rtype: uint + +.. c:function:: void pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags) + + Sets resolver-specific flags associated with a client object. + + :param pkgconf_client_t* client: The client object to set the resolver-specific flags on. + :return: nothing + +.. c:function:: const char *pkgconf_client_get_prefix_varname(const pkgconf_client_t *client) + + Retrieves the name of the variable that should contain a module's prefix. + In some cases, it is necessary to override this variable to allow proper path relocation. + + :param pkgconf_client_t* client: The client object to retrieve the prefix variable name from. + :return: the prefix variable name as a string + :rtype: const char * + +.. c:function:: void pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname) + + Sets the name of the variable that should contain a module's prefix. + If the variable name is ``NULL``, then the default variable name (``prefix``) is used. + + :param pkgconf_client_t* client: The client object to set the prefix variable name on. + :param char* prefix_varname: The prefix variable name to set. + :return: nothing + +.. c:function:: pkgconf_client_get_warn_handler(const pkgconf_client_t *client) + + Returns the warning handler if one is set, else ``NULL``. + + :param pkgconf_client_t* client: The client object to get the warn handler from. + :return: a function pointer to the warn handler or ``NULL`` + +.. c:function:: pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data) + + Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + + :param pkgconf_client_t* client: The client object to set the warn handler on. + :param pkgconf_error_handler_func_t warn_handler: The warn handler to set. + :param void* warn_handler_data: Optional data to associate with the warn handler. + :return: nothing + +.. c:function:: pkgconf_client_get_error_handler(const pkgconf_client_t *client) + + Returns the error handler if one is set, else ``NULL``. + + :param pkgconf_client_t* client: The client object to get the error handler from. + :return: a function pointer to the error handler or ``NULL`` + +.. c:function:: pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data) + + Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + + :param pkgconf_client_t* client: The client object to set the error handler on. + :param pkgconf_error_handler_func_t error_handler: The error handler to set. + :param void* error_handler_data: Optional data to associate with the error handler. + :return: nothing + +.. c:function:: pkgconf_client_get_trace_handler(const pkgconf_client_t *client) + + Returns the error handler if one is set, else ``NULL``. + + :param pkgconf_client_t* client: The client object to get the error handler from. + :return: a function pointer to the error handler or ``NULL`` + +.. c:function:: pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data) + + Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + + :param pkgconf_client_t* client: The client object to set the error handler on. + :param pkgconf_error_handler_func_t trace_handler: The error handler to set. + :param void* trace_handler_data: Optional data to associate with the error handler. + :return: nothing diff --git a/doc/libpkgconf-dependency.rst b/doc/libpkgconf-dependency.rst new file mode 100644 index 00000000000..ce70d6c11c3 --- /dev/null +++ b/doc/libpkgconf-dependency.rst @@ -0,0 +1,90 @@ + +libpkgconf `dependency` module +============================== + +The `dependency` module provides support for building `dependency lists` (the basic component of the overall `dependency graph`) and +`dependency nodes` which store dependency information. + +.. c:function:: pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare) + + Adds a parsed dependency to a dependency list as a dependency node. + + :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + :param pkgconf_list_t* list: The dependency list to add a dependency node to. + :param char* package: The package `atom` to set on the dependency node. + :param char* version: The package `version` to set on the dependency node. + :param pkgconf_pkg_comparator_t compare: The comparison operator to set on the dependency node. + :param uint flags: Any flags to attach to the dependency node. + :return: A dependency node. + :rtype: pkgconf_dependency_t * + +.. c:function:: void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail) + + Adds a dependency node to a pre-existing dependency list. + + :param pkgconf_list_t* list: The dependency list to add a dependency node to. + :param pkgconf_dependency_t* tail: The dependency node to add to the tail of the dependency list. + :return: nothing + +.. c:function:: void pkgconf_dependency_free_one(pkgconf_dependency_t *dep) + + Frees a dependency node. + + :param pkgconf_dependency_t* dep: The dependency node to free. + :return: nothing + +.. c:function:: pkgconf_dependency_t *pkgconf_dependency_ref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) + + Increases a dependency node's refcount. + + :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. + :param pkgconf_dependency_t* dep: The dependency to increase the refcount of. + :return: the dependency node on success, else NULL + +.. c:function:: void pkgconf_dependency_unref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) + + Decreases a dependency node's refcount and frees it if necessary. + + :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. + :param pkgconf_dependency_t* dep: The dependency to decrease the refcount of. + :return: nothing + +.. c:function:: void pkgconf_dependency_free(pkgconf_list_t *list) + + Release a dependency list and its child dependency nodes. + + :param pkgconf_list_t* list: The dependency list to release. + :return: nothing + +.. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends) + + Parse a dependency declaration into a dependency list. + Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed + into ``, zlib``. + + :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes. + :param char* depends: The dependency data to parse. + :param uint flags: Any flags to attach to the dependency nodes. + :return: nothing + +.. c:function:: void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends) + + Preprocess dependency data and then process that dependency declaration into a dependency list. + Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed + into ``, zlib``. + + :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + :param pkgconf_pkg_t* pkg: The package object that owns this dependency list. + :param pkgconf_list_t* deplist: The dependency list to populate with dependency nodes. + :param char* depends: The dependency data to parse. + :param uint flags: Any flags to attach to the dependency nodes. + :return: nothing + +.. c:function:: pkgconf_dependency_t *pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep) + + Copies a dependency node to a new one. + + :param pkgconf_client_t* client: The client object that will own this dependency. + :param pkgconf_dependency_t* dep: The dependency node to copy. + :return: a pointer to a new dependency node, else NULL diff --git a/doc/libpkgconf-fragment.rst b/doc/libpkgconf-fragment.rst new file mode 100644 index 00000000000..00759b7de96 --- /dev/null +++ b/doc/libpkgconf-fragment.rst @@ -0,0 +1,116 @@ + +libpkgconf `fragment` module +============================ + +The `fragment` module provides low-level management and rendering of fragment lists. A +`fragment list` contains various `fragments` of text (such as ``-I /usr/include``) in a matter +which is composable, mergeable and reorderable. + +.. c:function:: void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) + + Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set. + + :param pkgconf_client_t* client: The pkgconf client being accessed. + :param pkgconf_list_t* list: The fragment list. + :param char* string: The string of text to add as a fragment to the fragment list. + :param uint flags: Parsing-related flags for the package. + :return: nothing + +.. c:function:: bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag) + + Checks if a `fragment` contains a `system path`. System paths are detected at compile time and optionally overridden by + the ``PKG_CONFIG_SYSTEM_INCLUDE_PATH`` and ``PKG_CONFIG_SYSTEM_LIBRARY_PATH`` environment variables. + + :param pkgconf_client_t* client: The pkgconf client object the fragment belongs to. + :param pkgconf_fragment_t* frag: The fragment being checked. + :return: true if the fragment contains a system path, else false + :rtype: bool + +.. c:function:: void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private) + + Copies a `fragment` to another `fragment list`, possibly removing a previous copy of the `fragment` + in a process known as `mergeback`. + + :param pkgconf_client_t* client: The pkgconf client being accessed. + :param pkgconf_list_t* list: The list the fragment is being added to. + :param pkgconf_fragment_t* base: The fragment being copied. + :param bool is_private: Whether the fragment list is a `private` fragment list (static linking). + :return: nothing + +.. c:function:: void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) + + Copies a `fragment list` to another `fragment list`, possibly removing a previous copy of the fragments + in a process known as `mergeback`. + + :param pkgconf_client_t* client: The pkgconf client being accessed. + :param pkgconf_list_t* list: The list the fragments are being added to. + :param pkgconf_list_t* base: The list the fragments are being copied from. + :return: nothing + +.. c:function:: void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func) + + Copies a `fragment list` to another `fragment list` which match a user-specified filtering function. + + :param pkgconf_client_t* client: The pkgconf client being accessed. + :param pkgconf_list_t* dest: The destination list. + :param pkgconf_list_t* src: The source list. + :param pkgconf_fragment_filter_func_t filter_func: The filter function to use. + :param void* data: Optional data to pass to the filter function. + :return: nothing + +.. c:function:: size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) + + Calculates the required memory to store a `fragment list` when rendered as a string. + + :param pkgconf_list_t* list: The `fragment list` being rendered. + :param bool escape: Whether or not to escape special shell characters (deprecated). + :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + :return: the amount of bytes required to represent the `fragment list` when rendered + :rtype: size_t + +.. c:function:: void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops) + + Renders a `fragment list` into a buffer. + + :param pkgconf_list_t* list: The `fragment list` being rendered. + :param char* buf: The buffer to render the fragment list into. + :param size_t buflen: The length of the buffer. + :param bool escape: Whether or not to escape special shell characters (deprecated). + :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + :return: nothing + +.. c:function:: char *pkgconf_fragment_render(const pkgconf_list_t *list) + + Allocate memory and render a `fragment list` into it. + + :param pkgconf_list_t* list: The `fragment list` being rendered. + :param bool escape: Whether or not to escape special shell characters (deprecated). + :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + :return: An allocated string containing the rendered `fragment list`. + :rtype: char * + +.. c:function:: void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node) + + Delete a `fragment node` from a `fragment list`. + + :param pkgconf_list_t* list: The `fragment list` to delete from. + :param pkgconf_fragment_t* node: The `fragment node` to delete. + :return: nothing + +.. c:function:: void pkgconf_fragment_free(pkgconf_list_t *list) + + Delete an entire `fragment list`. + + :param pkgconf_list_t* list: The `fragment list` to delete. + :return: nothing + +.. c:function:: bool pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value) + + Parse a string into a `fragment list`. + + :param pkgconf_client_t* client: The pkgconf client being accessed. + :param pkgconf_list_t* list: The `fragment list` to add the fragment entries to. + :param pkgconf_list_t* vars: A list of variables to use for variable substitution. + :param uint flags: Any parsing flags to be aware of. + :param char* value: The string to parse into fragments. + :return: true on success, false on parse error diff --git a/doc/libpkgconf-path.rst b/doc/libpkgconf-path.rst new file mode 100644 index 00000000000..04789440a2d --- /dev/null +++ b/doc/libpkgconf-path.rst @@ -0,0 +1,71 @@ + +libpkgconf `path` module +======================== + +The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably, +it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment +variables. + +.. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist) + + Adds a path node to a path list. If the path is already in the list, do nothing. + + :param char* text: The path text to add as a path node. + :param pkgconf_list_t* dirlist: The path list to add the path node to. + :param bool filter: Whether to perform duplicate filtering. + :return: nothing + +.. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist) + + Splits a given text input and inserts paths into a path list. + + :param char* text: The path text to split and add as path nodes. + :param pkgconf_list_t* dirlist: The path list to have the path nodes added to. + :param bool filter: Whether to perform duplicate filtering. + :return: number of path nodes added to the path list + :rtype: size_t + +.. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist) + + Adds the paths specified in an environment variable to a path list. If the environment variable is not set, + an optional default set of paths is added. + + :param char* envvarname: The environment variable to look up. + :param char* fallback: The fallback paths to use if the environment variable is not set. + :param pkgconf_list_t* dirlist: The path list to add the path nodes to. + :param bool filter: Whether to perform duplicate filtering. + :return: number of path nodes added to the path list + :rtype: size_t + +.. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) + + Checks whether a path has a matching prefix in a path list. + + :param char* path: The path to check against a path list. + :param pkgconf_list_t* dirlist: The path list to check the path against. + :return: true if the path list has a matching prefix, otherwise false + :rtype: bool + +.. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) + + Copies a path list to another path list. + + :param pkgconf_list_t* dst: The path list to copy to. + :param pkgconf_list_t* src: The path list to copy from. + :return: nothing + +.. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist) + + Releases any path nodes attached to the given path list. + + :param pkgconf_list_t* dirlist: The path list to clean up. + :return: nothing + +.. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen) + + Relocates a path, possibly calling normpath() on it. + + :param char* buf: The path to relocate. + :param size_t buflen: The buffer length the path is contained in. + :return: true on success, false on error + :rtype: bool diff --git a/doc/libpkgconf-personality.rst b/doc/libpkgconf-personality.rst new file mode 100644 index 00000000000..134671ad121 --- /dev/null +++ b/doc/libpkgconf-personality.rst @@ -0,0 +1,27 @@ + +libpkgconf `personality` module +========================= + +.. c:function:: const pkgconf_cross_personality_t *pkgconf_cross_personality_default(void) + + Returns the default cross-compile personality. + + Not thread safe. + + :rtype: pkgconf_cross_personality_t* + :return: the default cross-compile personality + +.. c:function:: void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *) + + Decrements the count of default cross personality instances. + + Not thread safe. + + :rtype: void + +.. c:function:: pkgconf_cross_personality_t *pkgconf_cross_personality_find(const char *triplet) + + Attempts to find a cross-compile personality given a triplet. + + :rtype: pkgconf_cross_personality_t* + :return: the default cross-compile personality diff --git a/doc/libpkgconf-pkg.rst b/doc/libpkgconf-pkg.rst new file mode 100644 index 00000000000..044bd1f2f01 --- /dev/null +++ b/doc/libpkgconf-pkg.rst @@ -0,0 +1,154 @@ + +libpkgconf `pkg` module +======================= + +The `pkg` module provides dependency resolution services and the overall `.pc` file parsing +routines. + +.. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f, unsigned int flags) + + Parse a .pc file into a pkgconf_pkg_t object structure. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param char* filename: The filename of the package file (including full path). + :param FILE* f: The file object to read from. + :param uint flags: The flags to use when parsing. + :returns: A ``pkgconf_pkg_t`` object which contains the package data. + :rtype: pkgconf_pkg_t * + +.. c:function:: void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + + Releases all releases for a given ``pkgconf_pkg_t`` object. + + :param pkgconf_client_t* client: The client which owns the ``pkgconf_pkg_t`` object, `pkg`. + :param pkgconf_pkg_t* pkg: The package to free. + :return: nothing + +.. c:function:: pkgconf_pkg_t *pkgconf_pkg_ref(const pkgconf_client_t *client, pkgconf_pkg_t *pkg) + + Adds an additional reference to the package object. + + :param pkgconf_client_t* client: The pkgconf client object which owns the package being referenced. + :param pkgconf_pkg_t* pkg: The package object being referenced. + :return: The package itself with an incremented reference count. + :rtype: pkgconf_pkg_t * + +.. c:function:: void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + + Releases a reference on the package object. If the reference count is 0, then also free the package. + + :param pkgconf_client_t* client: The pkgconf client object which owns the package being dereferenced. + :param pkgconf_pkg_t* pkg: The package object being dereferenced. + :return: nothing + +.. c:function:: pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *data, pkgconf_pkg_iteration_func_t func) + + Iterates over all packages found in the `package directory list`, running ``func`` on them. If ``func`` returns true, + then stop iteration and return the last iterated package. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param void* data: An opaque pointer to data to provide the iteration function with. + :param pkgconf_pkg_iteration_func_t func: A function which is called for each package to determine if the package matches, + always return ``false`` to iterate over all packages. + :return: A package object reference if one is found by the scan function, else ``NULL``. + :rtype: pkgconf_pkg_t * + +.. c:function:: pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name) + + Search for a package. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param char* name: The name of the package `atom` to use for searching. + :return: A package object reference if the package was found, else ``NULL``. + :rtype: pkgconf_pkg_t * + +.. c:function:: int pkgconf_compare_version(const char *a, const char *b) + + Compare versions using RPM version comparison rules as described in the LSB. + + :param char* a: The first version to compare in the pair. + :param char* b: The second version to compare in the pair. + :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than. + :rtype: int + +.. c:function:: pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name) + + Looks up a built-in package. The package should not be freed or dereferenced. + + :param char* name: An atom corresponding to a built-in package to search for. + :return: the built-in package if present, else ``NULL``. + :rtype: pkgconf_pkg_t * + +.. c:function:: const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep) + + Returns the comparator used in a depgraph dependency node as a string. + + :param pkgconf_dependency_t* pkgdep: The depgraph dependency node to return the comparator for. + :return: A string matching the comparator or ``"???"``. + :rtype: char * + +.. c:function:: pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name) + + Look up the appropriate comparator bytecode in the comparator set (defined + in ``pkg.c``, see ``pkgconf_pkg_comparator_names`` and ``pkgconf_pkg_comparator_impls``). + + :param char* name: The comparator to look up by `name`. + :return: The comparator bytecode if found, else ``PKGCONF_CMP_ANY``. + :rtype: pkgconf_pkg_comparator_t + +.. c:function:: pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags) + + Verify a pkgconf_dependency_t node in the depgraph. If the dependency is solvable, + return the appropriate ``pkgconf_pkg_t`` object, else ``NULL``. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_dependency_t* pkgdep: The dependency graph node to solve. + :param uint* eflags: An optional pointer that, if set, will be populated with an error code from the resolver. + :return: On success, the appropriate ``pkgconf_pkg_t`` object to solve the dependency, else ``NULL``. + :rtype: pkgconf_pkg_t * + +.. c:function:: unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth) + + Verify the graph dependency nodes are satisfiable by walking the tree using + ``pkgconf_pkg_traverse()``. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* root: The root entry in the package dependency graph which should contain the top-level dependencies to resolve. + :param int depth: The maximum allowed depth for dependency resolution. + :return: On success, ``PKGCONF_PKG_ERRF_OK`` (0), else an error code. + :rtype: unsigned int + +.. c:function:: unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags) + + Walk and resolve the dependency graph up to `maxdepth` levels. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* root: The root of the dependency graph. + :param pkgconf_pkg_traverse_func_t func: A traversal function to call for each resolved node in the dependency graph. + :param void* data: An opaque pointer to data to be passed to the traversal function. + :param int maxdepth: The maximum depth to walk the dependency graph for. -1 means infinite recursion. + :param uint skip_flags: Skip over dependency nodes containing the specified flags. A setting of 0 skips no dependency nodes. + :return: ``PKGCONF_PKG_ERRF_OK`` on success, else an error code. + :rtype: unsigned int + +.. c:function:: int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) + + Walks a dependency graph and extracts relevant ``CFLAGS`` fragments. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* root: The root of the dependency graph. + :param pkgconf_list_t* list: The fragment list to add the extracted ``CFLAGS`` fragments to. + :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion. + :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code. + :rtype: unsigned int + +.. c:function:: int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) + + Walks a dependency graph and extracts relevant ``LIBS`` fragments. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* root: The root of the dependency graph. + :param pkgconf_list_t* list: The fragment list to add the extracted ``LIBS`` fragments to. + :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion. + :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code. + :rtype: unsigned int diff --git a/doc/libpkgconf-queue.rst b/doc/libpkgconf-queue.rst new file mode 100644 index 00000000000..43fca6960d2 --- /dev/null +++ b/doc/libpkgconf-queue.rst @@ -0,0 +1,78 @@ + +libpkgconf `queue` module +========================= + +The `queue` module provides an interface that allows easily building a dependency graph from an +arbitrary set of dependencies. It also provides support for doing "preflight" checks on the entire +dependency graph prior to working with it. + +Using the `queue` module functions is the recommended way of working with dependency graphs. + +.. c:function:: void pkgconf_queue_push(pkgconf_list_t *list, const char *package) + + Pushes a requested dependency onto the dependency resolver's queue. + + :param pkgconf_list_t* list: the dependency resolution queue to add the package request to. + :param char* package: the dependency atom requested + :return: nothing + +.. c:function:: bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list) + + Compile a dependency resolution queue into a dependency resolution problem if possible, otherwise report an error. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* world: The designated root of the dependency graph. + :param pkgconf_list_t* list: The list of dependency requests to consider. + :return: true if the built dependency resolution problem is consistent, else false + :rtype: bool + +.. c:function:: void pkgconf_queue_free(pkgconf_list_t *list) + + Release any memory related to a dependency resolution queue. + + :param pkgconf_list_t* list: The dependency resolution queue to release. + :return: nothing + +.. c:function:: void pkgconf_solution_free(pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth) + + Removes references to package nodes contained in a solution. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_pkg_t* world: The root for the generated dependency graph. Should have PKGCONF_PKG_PROPF_VIRTUAL flag. + :returns: nothing + +.. c:function:: bool pkgconf_queue_solve(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_pkg_t *world, int maxdepth) + + Solves and flattens the dependency graph for the supplied dependency list. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_list_t* list: The list of dependency requests to consider. + :param pkgconf_pkg_t* world: The root for the generated dependency graph, provided by the caller. Should have PKGCONF_PKG_PROPF_VIRTUAL flag. + :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + :returns: true if the dependency resolver found a solution, otherwise false. + :rtype: bool + +.. c:function:: void pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data) + + Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem and + feed the solution to a callback function if a complete dependency graph is found. + + This function should not be used in new code. Use pkgconf_queue_solve instead. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_list_t* list: The list of dependency requests to consider. + :param pkgconf_queue_apply_func_t func: The callback function to call if a solution is found by the dependency resolver. + :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + :param void* data: An opaque pointer which is passed to the callback function. + :returns: true if the dependency resolver found a solution, otherwise false. + :rtype: bool + +.. c:function:: void pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data) + + Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem. + + :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + :param pkgconf_list_t* list: The list of dependency requests to consider. + :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + :returns: true if the dependency resolver found a solution, otherwise false. + :rtype: bool diff --git a/doc/libpkgconf-tuple.rst b/doc/libpkgconf-tuple.rst new file mode 100644 index 00000000000..419168b0e46 --- /dev/null +++ b/doc/libpkgconf-tuple.rst @@ -0,0 +1,92 @@ + +libpkgconf `tuple` module +========================= + +The `tuple` module provides key-value mappings backed by a linked list. The key-value +mapping is mainly used for variable substitution when parsing .pc files. + +There are two sets of mappings: a ``pkgconf_pkg_t`` specific mapping, and a `global` mapping. +The `tuple` module provides convenience wrappers for managing the `global` mapping, which is +attached to a given client object. + +.. c:function:: void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value) + + Defines a global variable, replacing the previous declaration if one was set. + + :param pkgconf_client_t* client: The pkgconf client object to modify. + :param char* key: The key for the mapping (variable name). + :param char* value: The value for the mapped entry. + :return: nothing + +.. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key) + + Looks up a global variable. + + :param pkgconf_client_t* client: The pkgconf client object to access. + :param char* key: The key or variable name to look up. + :return: the contents of the variable or ``NULL`` + :rtype: char * + +.. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client) + + Delete all global variables associated with a pkgconf client object. + + :param pkgconf_client_t* client: The pkgconf client object to modify. + :return: nothing + +.. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv) + + Parse and define a global variable. + + :param pkgconf_client_t* client: The pkgconf client object to modify. + :param char* kv: The variable in the form of ``key=value``. + :return: nothing + +.. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse) + + Optionally parse and then define a variable. + + :param pkgconf_client_t* client: The pkgconf client object to access. + :param pkgconf_list_t* list: The variable list to add the new variable to. + :param char* key: The name of the variable being added. + :param char* value: The value of the variable being added. + :param bool parse: Whether or not to parse the value for variable substitution. + :return: a variable object + :rtype: pkgconf_tuple_t * + +.. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) + + Look up a variable in a variable list. + + :param pkgconf_client_t* client: The pkgconf client object to access. + :param pkgconf_list_t* list: The variable list to search. + :param char* key: The variable name to search for. + :return: the value of the variable or ``NULL`` + :rtype: char * + +.. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags) + + Parse an expression for variable substitution. + + :param pkgconf_client_t* client: The pkgconf client object to access. + :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list). + :param char* value: The ``key=value`` string to parse. + :param uint flags: Any flags to consider while parsing. + :return: the variable data with any variables substituted + :rtype: char * + +.. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) + + Deletes a variable object, removing it from any variable lists and releasing any memory associated + with it. + + :param pkgconf_tuple_t* tuple: The variable object to release. + :param pkgconf_list_t* list: The variable list the variable object is attached to. + :return: nothing + +.. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list) + + Deletes a variable list and any variables attached to it. + + :param pkgconf_list_t* list: The variable list to delete. + :return: nothing diff --git a/doc/libpkgconf.rst b/doc/libpkgconf.rst new file mode 100644 index 00000000000..37167f0f25f --- /dev/null +++ b/doc/libpkgconf.rst @@ -0,0 +1,17 @@ +libpkgconf - an API for managing `pkg-config` modules +===================================================== + +.. toctree:: + :maxdepth: 2 + + libpkgconf-argvsplit + libpkgconf-audit + libpkgconf-cache + libpkgconf-client + libpkgconf-dependency + libpkgconf-fragment + libpkgconf-path + libpkgconf-personality + libpkgconf-pkg + libpkgconf-queue + libpkgconf-tuple diff --git a/libpkgconf.pc.in b/libpkgconf.pc.in new file mode 100644 index 00000000000..119fe57f827 --- /dev/null +++ b/libpkgconf.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=${prefix} +includedir=@includedir@ +libdir=@libdir@ + +Name: libpkgconf +Description: a library for accessing and manipulating development framework configuration +URL: https://gitea.treehouse.systems/ariadne/pkgconf +License: ISC +Version: @PACKAGE_VERSION@ +CFlags: -I${includedir}/pkgconf +Libs: -L${libdir} -lpkgconf diff --git a/libpkgconf/argvsplit.c b/libpkgconf/argvsplit.c new file mode 100644 index 00000000000..1ff221ac63f --- /dev/null +++ b/libpkgconf/argvsplit.c @@ -0,0 +1,161 @@ +/* + * argvsplit.c + * argv_split() routine + * + * Copyright (c) 2012, 2017 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `argvsplit` module + * ============================= + * + * This is a lowlevel module which provides parsing of strings into argument vectors, + * similar to what a shell would do. + */ + +/* + * !doc + * + * .. c:function:: void pkgconf_argv_free(char **argv) + * + * Frees an argument vector. + * + * :param char** argv: The argument vector to free. + * :return: nothing + */ +void +pkgconf_argv_free(char **argv) +{ + free(argv[0]); + free(argv); +} + +/* + * !doc + * + * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv) + * + * Splits a string into an argument vector. + * + * :param char* src: The string to split. + * :param int* argc: A pointer to an integer to store the argument count. + * :param char*** argv: A pointer to a pointer for an argument vector. + * :return: 0 on success, -1 on error. + * :rtype: int + */ +int +pkgconf_argv_split(const char *src, int *argc, char ***argv) +{ + char *buf = calloc(1, strlen(src) + 1); + if (buf == NULL) + return -1; + + const char *src_iter; + char *dst_iter; + int argc_count = 0; + int argv_size = 5; + char quote = 0; + bool escaped = false; + + src_iter = src; + dst_iter = buf; + + *argv = calloc(argv_size, sizeof (void *)); + if (*argv == NULL) + { + free(buf); + return -1; + } + + (*argv)[argc_count] = dst_iter; + + while (*src_iter) + { + if (escaped) + { + /* POSIX: only \CHAR is special inside a double quote if CHAR is {$, `, ", \, newline}. */ + if (quote == '"') + { + if (!(*src_iter == '$' || *src_iter == '`' || *src_iter == '"' || *src_iter == '\\')) + *dst_iter++ = '\\'; + + *dst_iter++ = *src_iter; + } + else + { + *dst_iter++ = *src_iter; + } + + escaped = false; + } + else if (quote) + { + if (*src_iter == quote) + quote = 0; + else if (*src_iter == '\\' && quote != '\'') + escaped = true; + else + *dst_iter++ = *src_iter; + } + else if (isspace((unsigned char)*src_iter)) + { + if ((*argv)[argc_count] != NULL) + { + argc_count++, dst_iter++; + + if (argc_count == argv_size) + { + argv_size += 5; + *argv = realloc(*argv, sizeof(void *) * argv_size); + } + + (*argv)[argc_count] = dst_iter; + } + } + else switch(*src_iter) + { + case '\\': + escaped = true; + break; + + case '\"': + case '\'': + quote = *src_iter; + break; + + default: + *dst_iter++ = *src_iter; + break; + } + + src_iter++; + } + + if (escaped || quote) + { + free(*argv); + free(buf); + return -1; + } + + if (strlen((*argv)[argc_count])) + { + argc_count++; + } + + *argc = argc_count; + return 0; +} diff --git a/libpkgconf/audit.c b/libpkgconf/audit.c new file mode 100644 index 00000000000..a06eb24fc72 --- /dev/null +++ b/libpkgconf/audit.c @@ -0,0 +1,98 @@ +/* + * audit.c + * package audit log functions + * + * Copyright (c) 2016 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include + +/* + * !doc + * + * libpkgconf `audit` module + * ========================= + * + * The libpkgconf `audit` module contains the functions related to attaching an audit log file + * to a ``pkgconf_client_t`` object. + * + * The audit log format is the same as the output generated by the ``PKG_CONFIG_LOG`` environment + * variable. + */ + +/* + * !doc + * + * .. c:function:: void pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf) + * + * Sets the audit log file pointer on `client` to `auditf`. + * The callee is responsible for closing any previous log files. + * + * :param pkgconf_client_t* client: The client object to modify. + * :param FILE* auditf: The file pointer for the already open log file. + * :return: nothing + */ +void +pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf) +{ + client->auditf = auditf; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...) + * + * Logs a message to the opened audit log (if any). + * + * :param pkgconf_client_t* client: The client object the log message is for. + * :param char* format: The format string to use for the log messages. + * :return: nothing + */ +void +pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...) +{ + va_list va; + + if (client->auditf == NULL) + return; + + va_start(va, format); + vfprintf(client->auditf, format, va); + va_end(va); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode) + * + * Convenience function which logs a dependency node to the opened audit log (if any). + * + * :param pkgconf_client_t* client: The client object the log message is for. + * :param pkgconf_pkg_t* dep: The dependency package object being logged. + * :param pkgconf_dependency_t* depnode: The dependency object itself being logged. + * :return: nothing + */ +void +pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode) +{ + if (client->auditf == NULL) + return; + + fprintf(client->auditf, "%s ", dep->id); + if (depnode->version != NULL && depnode->compare != PKGCONF_CMP_ANY) + { + fprintf(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version); + } + + fprintf(client->auditf, "[%s]\n", dep->version); +} diff --git a/libpkgconf/bsdstubs.c b/libpkgconf/bsdstubs.c new file mode 100644 index 00000000000..a5291f7a5a9 --- /dev/null +++ b/libpkgconf/bsdstubs.c @@ -0,0 +1,197 @@ +/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */ +/* $OpenBSD: strlcat.c,v 1.12 2005/03/30 20:13:52 otto Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include +#include + +#if !HAVE_DECL_STRLCPY +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +static inline size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} +#endif + +#if !HAVE_DECL_STRLCAT +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +static inline size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} +#endif + +/* + * Copyright (c) 2012 William Pitcock . + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#if !HAVE_DECL_STRNDUP +/* + * Creates a memory buffer and copies at most 'len' characters to it. + * If 'len' is less than the length of the source string, truncation occured. + */ +static inline char * +strndup(const char *src, size_t len) +{ + char *out = malloc(len + 1); + pkgconf_strlcpy(out, src, len + 1); + return out; +} +#endif + +#if !HAVE_DECL_PLEDGE +static inline int +pledge(const char *promises, const char *execpromises) +{ + (void) promises; + (void) execpromises; + + return 0; +} +#endif + +#if !HAVE_DECL_UNVEIL +static inline int +unveil(const char *path, const char *permissions) +{ + (void) path; + (void) permissions; + + return 0; +} +#endif + +size_t +pkgconf_strlcpy(char *dst, const char *src, size_t siz) +{ + return strlcpy(dst, src, siz); +} + +size_t +pkgconf_strlcat(char *dst, const char *src, size_t siz) +{ + return strlcat(dst, src, siz); +} + +char * +pkgconf_strndup(const char *src, size_t len) +{ + return strndup(src, len); +} + +#if !HAVE_DECL_REALLOCARRAY +void * +reallocarray(void *ptr, size_t m, size_t n) +{ + if (n && m > -1 / n) + { + errno = ENOMEM; + return 0; + } + + return realloc(ptr, m * n); +} +#endif + +void * +pkgconf_reallocarray(void *ptr, size_t m, size_t n) +{ + return reallocarray(ptr, m, n); +} + +int +pkgconf_pledge(const char *promises, const char *execpromises) +{ + return pledge(promises, execpromises); +} + +int +pkgconf_unveil(const char *path, const char *permissions) +{ + return unveil(path, permissions); +} diff --git a/libpkgconf/bsdstubs.h b/libpkgconf/bsdstubs.h new file mode 100644 index 00000000000..21b9432a4a5 --- /dev/null +++ b/libpkgconf/bsdstubs.h @@ -0,0 +1,36 @@ +/* + * bsdstubs.h + * Header for stub BSD function prototypes if unavailable on a specific platform. + * + * Copyright (c) 2012 William Pitcock . + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBPKGCONF_BSDSTUBS_H +#define LIBPKGCONF_BSDSTUBS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +PKGCONF_API extern size_t pkgconf_strlcpy(char *dst, const char *src, size_t siz); +PKGCONF_API extern size_t pkgconf_strlcat(char *dst, const char *src, size_t siz); +PKGCONF_API extern char *pkgconf_strndup(const char *src, size_t len); +PKGCONF_API extern void *pkgconf_reallocarray(void *ptr, size_t m, size_t n); +PKGCONF_API extern int pkgconf_pledge(const char *promises, const char *execpromises); +PKGCONF_API extern int pkgconf_unveil(const char *path, const char *permissions); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libpkgconf/buffer.c b/libpkgconf/buffer.c new file mode 100644 index 00000000000..6857022db41 --- /dev/null +++ b/libpkgconf/buffer.c @@ -0,0 +1,87 @@ +/* + * buffer.c + * dynamically-managed buffers + * + * Copyright (c) 2024 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `buffer` module + * ========================== + * + * The libpkgconf `buffer` module contains the functions related to managing + * dynamically-allocated buffers. + */ + +static inline size_t +target_allocation_size(size_t target_size) +{ + return 4096 + (4096 * (target_size / 4096)); +} + +void +pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text) +{ + size_t needed = strlen(text) + 1; + size_t newsize = pkgconf_buffer_len(buffer) + needed; + + char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + + /* XXX: silently failing here is antisocial */ + if (newbase == NULL) + return; + + char *newend = newbase + pkgconf_buffer_len(buffer); + pkgconf_strlcpy(newend, text, needed); + + buffer->base = newbase; + buffer->end = newend + needed; +} + +void +pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte) +{ + size_t newsize = pkgconf_buffer_len(buffer) + 1; + char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + + /* XXX: silently failing here remains antisocial */ + if (newbase == NULL) + return; + + char *newend = newbase + newsize; + *(newend - 1) = byte; + *newend = '\0'; + + buffer->base = newbase; + buffer->end = newend; +} + +void +pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer) +{ + size_t newsize = pkgconf_buffer_len(buffer) - 1; + char *newbase = realloc(buffer->base, target_allocation_size(newsize)); + + buffer->base = newbase; + buffer->end = newbase + newsize; + *(buffer->end) = '\0'; +} + +void +pkgconf_buffer_finalize(pkgconf_buffer_t *buffer) +{ + free(buffer->base); +} diff --git a/libpkgconf/cache.c b/libpkgconf/cache.c new file mode 100644 index 00000000000..883c8df7127 --- /dev/null +++ b/libpkgconf/cache.c @@ -0,0 +1,231 @@ +/* + * cache.c + * package object cache + * + * Copyright (c) 2013 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +#include + +/* + * !doc + * + * libpkgconf `cache` module + * ========================= + * + * The libpkgconf `cache` module manages a package/module object cache, allowing it to + * avoid loading duplicate copies of a package/module. + * + * A cache is tied to a specific pkgconf client object, so package objects should not + * be shared across threads. + */ + +static int +cache_member_cmp(const void *a, const void *b) +{ + const char *key = a; + const pkgconf_pkg_t *pkg = *(void **) b; + + return strcmp(key, pkg->id); +} + +static int +cache_member_sort_cmp(const void *a, const void *b) +{ + const pkgconf_pkg_t *pkgA = *(void **) a; + const pkgconf_pkg_t *pkgB = *(void **) b; + + if (pkgA == NULL) + return 1; + + if (pkgB == NULL) + return -1; + + return strcmp(pkgA->id, pkgB->id); +} + +static void +cache_dump(const pkgconf_client_t *client) +{ + size_t i; + + PKGCONF_TRACE(client, "dumping package cache contents"); + + for (i = 0; i < client->cache_count; i++) + { + const pkgconf_pkg_t *pkg = client->cache_table[i]; + + PKGCONF_TRACE(client, SIZE_FMT_SPECIFIER": %p(%s)", + i, pkg, pkg == NULL ? "NULL" : pkg->id); + } +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_cache_lookup(const pkgconf_client_t *client, const char *id) + * + * Looks up a package in the cache given an `id` atom, + * such as ``gtk+-3.0`` and returns the already loaded version + * if present. + * + * :param pkgconf_client_t* client: The client object to access. + * :param char* id: The package atom to look up in the client object's cache. + * :return: A package object if present, else ``NULL``. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_cache_lookup(pkgconf_client_t *client, const char *id) +{ + if (client->cache_table == NULL) + return NULL; + + pkgconf_pkg_t **pkg; + + pkg = bsearch(id, client->cache_table, + client->cache_count, sizeof (void *), + cache_member_cmp); + + if (pkg != NULL) + { + PKGCONF_TRACE(client, "found: %s @%p", id, *pkg); + return pkgconf_pkg_ref(client, *pkg); + } + + PKGCONF_TRACE(client, "miss: %s", id); + return NULL; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Adds an entry for the package to the package cache. + * The cache entry must be removed if the package is freed. + * + * :param pkgconf_client_t* client: The client object to modify. + * :param pkgconf_pkg_t* pkg: The package object to add to the client object's cache. + * :return: nothing + */ +void +pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + if (pkg == NULL) + return; + + pkgconf_pkg_ref(client, pkg); + + PKGCONF_TRACE(client, "added @%p to cache", pkg); + + /* mark package as cached */ + pkg->flags |= PKGCONF_PKG_PROPF_CACHED; + + ++client->cache_count; + client->cache_table = pkgconf_reallocarray(client->cache_table, + client->cache_count, sizeof (void *)); + client->cache_table[client->cache_count - 1] = pkg; + + qsort(client->cache_table, client->cache_count, + sizeof(void *), cache_member_sort_cmp); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Deletes a package from the client object's package cache. + * + * :param pkgconf_client_t* client: The client object to modify. + * :param pkgconf_pkg_t* pkg: The package object to remove from the client object's cache. + * :return: nothing + */ +void +pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + if (client->cache_table == NULL) + return; + + if (pkg == NULL) + return; + + if (!(pkg->flags & PKGCONF_PKG_PROPF_CACHED)) + return; + + PKGCONF_TRACE(client, "removed @%p from cache", pkg); + + pkgconf_pkg_t **slot; + + slot = bsearch(pkg->id, client->cache_table, + client->cache_count, sizeof (void *), + cache_member_cmp); + + if (slot == NULL) + return; + + (*slot)->flags &= ~PKGCONF_PKG_PROPF_CACHED; + pkgconf_pkg_unref(client, *slot); + *slot = NULL; + + qsort(client->cache_table, client->cache_count, + sizeof(void *), cache_member_sort_cmp); + + if (client->cache_table[client->cache_count - 1] != NULL) + { + PKGCONF_TRACE(client, "end of cache table refers to %p, not NULL", + client->cache_table[client->cache_count - 1]); + cache_dump(client); + abort(); + } + + client->cache_count--; + if (client->cache_count > 0) + { + client->cache_table = pkgconf_reallocarray(client->cache_table, + client->cache_count, sizeof(void *)); + } + else + { + free(client->cache_table); + client->cache_table = NULL; + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_cache_free(pkgconf_client_t *client) + * + * Releases all resources related to a client object's package cache. + * This function should only be called to clear a client object's package cache, + * as it may release any package in the cache. + * + * :param pkgconf_client_t* client: The client object to modify. + */ +void +pkgconf_cache_free(pkgconf_client_t *client) +{ + if (client->cache_table == NULL) + return; + + while (client->cache_count > 0) + pkgconf_cache_remove(client, client->cache_table[0]); + + free(client->cache_table); + client->cache_table = NULL; + client->cache_count = 0; + + PKGCONF_TRACE(client, "cleared package cache"); +} diff --git a/libpkgconf/client.c b/libpkgconf/client.c new file mode 100644 index 00000000000..4fe36ecd9af --- /dev/null +++ b/libpkgconf/client.c @@ -0,0 +1,817 @@ +/* + * client.c + * libpkgconf consumer lifecycle management + * + * Copyright (c) 2016 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +/* + * !doc + * + * libpkgconf `client` module + * ========================== + * + * The libpkgconf `client` module implements the `pkgconf_client_t` "client" object. + * Client objects store all necessary state for libpkgconf allowing for multiple instances to run + * in parallel. + * + * Client objects are not thread safe, in other words, a client object should not be shared across + * thread boundaries. + */ + +static void +trace_path_list(const pkgconf_client_t *client, const char *desc, pkgconf_list_t *list) +{ + const pkgconf_node_t *n; + + PKGCONF_TRACE(client, "%s:", desc); + PKGCONF_FOREACH_LIST_ENTRY(list->head, n) + { + const pkgconf_path_t *p = n->data; + + PKGCONF_TRACE(client, " - '%s'", p->path); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_dir_list_build(pkgconf_client_t *client) + * + * Bootstraps the package search paths. If the ``PKGCONF_PKG_PKGF_ENV_ONLY`` `flag` is set on the client, + * then only the ``PKG_CONFIG_PATH`` environment variable will be used, otherwise both the + * ``PKG_CONFIG_PATH`` and ``PKG_CONFIG_LIBDIR`` environment variables will be used. + * + * :param pkgconf_client_t* client: The pkgconf client object to bootstrap. + * :return: nothing + */ +void +pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality) +{ + pkgconf_path_build_from_environ("PKG_CONFIG_PATH", NULL, &client->dir_list, true); + + if (!(client->flags & PKGCONF_PKG_PKGF_ENV_ONLY)) + { + pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER; + const pkgconf_list_t *prepend_list = &personality->dir_list; + +#ifdef _WIN32 + (void) pkgconf_path_build_from_registry(HKEY_CURRENT_USER, &client->dir_list, true); + (void) pkgconf_path_build_from_registry(HKEY_LOCAL_MACHINE, &client->dir_list, true); +#endif + + if (getenv("PKG_CONFIG_LIBDIR") != NULL) + { + /* PKG_CONFIG_LIBDIR= should empty the search path entirely. */ + (void) pkgconf_path_build_from_environ("PKG_CONFIG_LIBDIR", NULL, &dir_list, true); + prepend_list = &dir_list; + } + + pkgconf_path_copy_list(&client->dir_list, prepend_list); + pkgconf_path_free(&dir_list); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) + * + * Initialise a pkgconf client object. + * + * :param pkgconf_client_t* client: The client to initialise. + * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. + * :param void* error_handler_data: user data passed to optional error handler + * :param pkgconf_cross_personality_t* personality: the cross-compile personality to use for defaults + * :return: nothing + */ +void +pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) +{ + client->error_handler_data = error_handler_data; + client->error_handler = error_handler; + client->auditf = NULL; + client->cache_table = NULL; + client->cache_count = 0; + +#ifndef PKGCONF_LITE + if (client->trace_handler == NULL) + pkgconf_client_set_trace_handler(client, NULL, NULL); +#endif + + if (client->unveil_handler == NULL) + pkgconf_client_set_unveil_handler(client, NULL); + + pkgconf_client_set_error_handler(client, error_handler, error_handler_data); + pkgconf_client_set_warn_handler(client, NULL, NULL); + + pkgconf_client_set_sysroot_dir(client, personality->sysroot_dir); + pkgconf_client_set_buildroot_dir(client, NULL); + pkgconf_client_set_prefix_varname(client, NULL); + + if(getenv("PKG_CONFIG_SYSTEM_LIBRARY_PATH") == NULL) + pkgconf_path_copy_list(&client->filter_libdirs, &personality->filter_libdirs); + else + pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_LIBRARY_PATH", NULL, &client->filter_libdirs, false); + + if(getenv("PKG_CONFIG_SYSTEM_INCLUDE_PATH") == NULL) + pkgconf_path_copy_list(&client->filter_includedirs, &personality->filter_includedirs); + else + pkgconf_path_build_from_environ("PKG_CONFIG_SYSTEM_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + + /* GCC uses these environment variables to define system include paths, so we should check them. */ +#ifdef __HAIKU__ + pkgconf_path_build_from_environ("BELIBRARIES", NULL, &client->filter_libdirs, false); +#else + pkgconf_path_build_from_environ("LIBRARY_PATH", NULL, &client->filter_libdirs, false); +#endif + pkgconf_path_build_from_environ("CPATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ("C_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ("CPLUS_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + pkgconf_path_build_from_environ("OBJC_INCLUDE_PATH", NULL, &client->filter_includedirs, false); + +#ifdef _WIN32 + /* also use the path lists that MSVC uses on windows */ + pkgconf_path_build_from_environ("INCLUDE", NULL, &client->filter_includedirs, false); +#endif + + PKGCONF_TRACE(client, "initialized client @%p", client); + + trace_path_list(client, "filtered library paths", &client->filter_libdirs); + trace_path_list(client, "filtered include paths", &client->filter_includedirs); +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_t* pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) + * + * Allocate and initialise a pkgconf client object. + * + * :param pkgconf_error_handler_func_t error_handler: An optional error handler to use for logging errors. + * :param void* error_handler_data: user data passed to optional error handler + * :param pkgconf_cross_personality_t* personality: cross-compile personality to use + * :return: A pkgconf client object. + * :rtype: pkgconf_client_t* + */ +pkgconf_client_t * +pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality) +{ + pkgconf_client_t *out = calloc(1, sizeof(pkgconf_client_t)); + if (out == NULL) + return NULL; + + pkgconf_client_init(out, error_handler, error_handler_data, personality); + return out; +} + +static void +unref_preload_list(pkgconf_client_t *client) +{ + pkgconf_node_t *n, *tn; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(client->preloaded_pkgs.head, n, tn) + { + pkgconf_pkg_t *pkg = n->data; + pkgconf_pkg_unref(client, pkg); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_deinit(pkgconf_client_t *client) + * + * Release resources belonging to a pkgconf client object. + * + * :param pkgconf_client_t* client: The client to deinitialise. + * :return: nothing + */ +void +pkgconf_client_deinit(pkgconf_client_t *client) +{ + PKGCONF_TRACE(client, "deinit @%p", client); + + unref_preload_list(client); + + if (client->prefix_varname != NULL) + free(client->prefix_varname); + + if (client->sysroot_dir != NULL) + free(client->sysroot_dir); + + if (client->buildroot_dir != NULL) + free(client->buildroot_dir); + + pkgconf_path_free(&client->filter_libdirs); + pkgconf_path_free(&client->filter_includedirs); + + pkgconf_tuple_free_global(client); + pkgconf_path_free(&client->dir_list); + pkgconf_cache_free(client); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_free(pkgconf_client_t *client) + * + * Release resources belonging to a pkgconf client object and then free the client object itself. + * + * :param pkgconf_client_t* client: The client to deinitialise and free. + * :return: nothing + */ +void +pkgconf_client_free(pkgconf_client_t *client) +{ + pkgconf_client_deinit(client); + free(client); +} + +/* + * !doc + * + * .. c:function:: const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client) + * + * Retrieves the client's sysroot directory (if any). + * + * :param pkgconf_client_t* client: The client object being accessed. + * :return: A string containing the sysroot directory or NULL. + * :rtype: const char * + */ +const char * +pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client) +{ + return client->sysroot_dir; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir) + * + * Sets or clears the sysroot directory on a client object. Any previous sysroot directory setting is + * automatically released if one was previously set. + * + * Additionally, the global tuple ``$(pc_sysrootdir)`` is set as appropriate based on the new setting. + * + * :param pkgconf_client_t* client: The client object being modified. + * :param char* sysroot_dir: The sysroot directory to set or NULL to unset. + * :return: nothing + */ +void +pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir) +{ + if (client->sysroot_dir != NULL) + free(client->sysroot_dir); + + client->sysroot_dir = sysroot_dir != NULL ? strdup(sysroot_dir) : NULL; + + PKGCONF_TRACE(client, "set sysroot_dir to: %s", client->sysroot_dir != NULL ? client->sysroot_dir : ""); + + pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : "/"); +} + +/* + * !doc + * + * .. c:function:: const char *pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client) + * + * Retrieves the client's buildroot directory (if any). + * + * :param pkgconf_client_t* client: The client object being accessed. + * :return: A string containing the buildroot directory or NULL. + * :rtype: const char * + */ +const char * +pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client) +{ + return client->buildroot_dir; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir) + * + * Sets or clears the buildroot directory on a client object. Any previous buildroot directory setting is + * automatically released if one was previously set. + * + * Additionally, the global tuple ``$(pc_top_builddir)`` is set as appropriate based on the new setting. + * + * :param pkgconf_client_t* client: The client object being modified. + * :param char* buildroot_dir: The buildroot directory to set or NULL to unset. + * :return: nothing + */ +void +pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir) +{ + if (client->buildroot_dir != NULL) + free(client->buildroot_dir); + + client->buildroot_dir = buildroot_dir != NULL ? strdup(buildroot_dir) : NULL; + + PKGCONF_TRACE(client, "set buildroot_dir to: %s", client->buildroot_dir != NULL ? client->buildroot_dir : ""); + + pkgconf_tuple_add_global(client, "pc_top_builddir", client->buildroot_dir != NULL ? client->buildroot_dir : "$(top_builddir)"); +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) + * + * Report an error to a client-registered error handler. + * + * :param pkgconf_client_t* client: The pkgconf client object to report the error to. + * :param char* format: A printf-style format string to use for formatting the error message. + * :return: true if the error handler processed the message, else false. + * :rtype: bool + */ +bool +pkgconf_error(const pkgconf_client_t *client, const char *format, ...) +{ + char *errbuf; + ssize_t msgsize = 0; + bool ret; + va_list va; + + va_start(va, format); + msgsize = vsnprintf(NULL, 0, format, va); + va_end(va); + + if (msgsize < 0) + return false; + + msgsize++; + + errbuf = calloc(1, msgsize); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, msgsize, format, va); + va_end(va); + + ret = client->error_handler(errbuf, client, client->error_handler_data); + free(errbuf); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) + * + * Report an error to a client-registered warn handler. + * + * :param pkgconf_client_t* client: The pkgconf client object to report the error to. + * :param char* format: A printf-style format string to use for formatting the warning message. + * :return: true if the warn handler processed the message, else false. + * :rtype: bool + */ +bool +pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) +{ + char *errbuf; + ssize_t msgsize = 0; + bool ret; + va_list va; + + va_start(va, format); + msgsize = vsnprintf(NULL, 0, format, va); + va_end(va); + + if (msgsize < 0) + return false; + + msgsize++; + + errbuf = calloc(1, msgsize); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, msgsize, format, va); + va_end(va); + + ret = client->warn_handler(errbuf, client, client->warn_handler_data); + free(errbuf); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t len, const char *funcname, const char *format, ...) + * + * Report a message to a client-registered trace handler. + * + * :param pkgconf_client_t* client: The pkgconf client object to report the trace message to. + * :param char* filename: The file the function is in. + * :param size_t lineno: The line number currently being executed. + * :param char* funcname: The function name to use. + * :param char* format: A printf-style format string to use for formatting the trace message. + * :return: true if the trace handler processed the message, else false. + * :rtype: bool + */ +bool +pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...) +{ + char prefix[PKGCONF_ITEM_SIZE]; + char *errbuf = NULL; + ssize_t errlen; + char *finalbuf = NULL; + ssize_t finallen; + bool ret; + va_list va; + + if (client == NULL || client->trace_handler == NULL) + return false; + + snprintf(prefix, sizeof prefix, "%s:" SIZE_FMT_SPECIFIER " [%s]:", filename, lineno, funcname); + + va_start(va, format); + errlen = vsnprintf(NULL, 0, format, va); + va_end(va); + + if (errlen < 0) + return false; + + errlen++; + errbuf = calloc(1, errlen); + if (errbuf == NULL) + return false; + + va_start(va, format); + vsnprintf(errbuf, errlen, format, va); + va_end(va); + + finallen = snprintf(NULL, 0, "%s %s\n", prefix, errbuf); + if (finallen < 0) + return false; + + finallen++; + finalbuf = calloc(1, finallen); + if (finalbuf == NULL) + return false; + + snprintf(finalbuf, finallen, "%s %s\n", prefix, errbuf); + ret = client->trace_handler(finalbuf, client, client->trace_handler_data); + free(errbuf); + free(finalbuf); + + return ret; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, const void *data) + * + * The default pkgconf error handler. + * + * :param char* msg: The error message to handle. + * :param pkgconf_client_t* client: The client object the error originated from. + * :param void* data: An opaque pointer to extra data associated with the client for error handling. + * :return: true (the function does nothing to process the message) + * :rtype: bool + */ +bool +pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, void *data) +{ + (void) msg; + (void) client; + (void) data; + + return true; +} + +static void +default_unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions) +{ + (void) client; + (void) path; + (void) permissions; +} + +/* + * !doc + * + * .. c:function:: unsigned int pkgconf_client_get_flags(const pkgconf_client_t *client) + * + * Retrieves resolver-specific flags associated with a client object. + * + * :param pkgconf_client_t* client: The client object to retrieve the resolver-specific flags from. + * :return: a bitfield of resolver-specific flags + * :rtype: uint + */ +unsigned int +pkgconf_client_get_flags(const pkgconf_client_t *client) +{ + return client->flags; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags) + * + * Sets resolver-specific flags associated with a client object. + * + * :param pkgconf_client_t* client: The client object to set the resolver-specific flags on. + * :return: nothing + */ +void +pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags) +{ + client->flags = flags; +} + +/* + * !doc + * + * .. c:function:: const char *pkgconf_client_get_prefix_varname(const pkgconf_client_t *client) + * + * Retrieves the name of the variable that should contain a module's prefix. + * In some cases, it is necessary to override this variable to allow proper path relocation. + * + * :param pkgconf_client_t* client: The client object to retrieve the prefix variable name from. + * :return: the prefix variable name as a string + * :rtype: const char * + */ +const char * +pkgconf_client_get_prefix_varname(const pkgconf_client_t *client) +{ + return client->prefix_varname; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname) + * + * Sets the name of the variable that should contain a module's prefix. + * If the variable name is ``NULL``, then the default variable name (``prefix``) is used. + * + * :param pkgconf_client_t* client: The client object to set the prefix variable name on. + * :param char* prefix_varname: The prefix variable name to set. + * :return: nothing + */ +void +pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname) +{ + if (prefix_varname == NULL) + prefix_varname = "prefix"; + + if (client->prefix_varname != NULL) + free(client->prefix_varname); + + client->prefix_varname = strdup(prefix_varname); + + PKGCONF_TRACE(client, "set prefix_varname to: %s", client->prefix_varname); +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_get_warn_handler(const pkgconf_client_t *client) + * + * Returns the warning handler if one is set, else ``NULL``. + * + * :param pkgconf_client_t* client: The client object to get the warn handler from. + * :return: a function pointer to the warn handler or ``NULL`` + */ +pkgconf_error_handler_func_t +pkgconf_client_get_warn_handler(const pkgconf_client_t *client) +{ + return client->warn_handler; +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data) + * + * Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + * + * :param pkgconf_client_t* client: The client object to set the warn handler on. + * :param pkgconf_error_handler_func_t warn_handler: The warn handler to set. + * :param void* warn_handler_data: Optional data to associate with the warn handler. + * :return: nothing + */ +void +pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data) +{ + client->warn_handler = warn_handler; + client->warn_handler_data = warn_handler_data; + + if (client->warn_handler == NULL) + { + PKGCONF_TRACE(client, "installing default warn handler"); + client->warn_handler = pkgconf_default_error_handler; + } +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_get_error_handler(const pkgconf_client_t *client) + * + * Returns the error handler if one is set, else ``NULL``. + * + * :param pkgconf_client_t* client: The client object to get the error handler from. + * :return: a function pointer to the error handler or ``NULL`` + */ +pkgconf_error_handler_func_t +pkgconf_client_get_error_handler(const pkgconf_client_t *client) +{ + return client->error_handler; +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data) + * + * Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + * + * :param pkgconf_client_t* client: The client object to set the error handler on. + * :param pkgconf_error_handler_func_t error_handler: The error handler to set. + * :param void* error_handler_data: Optional data to associate with the error handler. + * :return: nothing + */ +void +pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data) +{ + client->error_handler = error_handler; + client->error_handler_data = error_handler_data; + + if (client->error_handler == NULL) + { + PKGCONF_TRACE(client, "installing default error handler"); + client->error_handler = pkgconf_default_error_handler; + } +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_get_unveil_handler(const pkgconf_client_t *client) + * + * Returns the unveil handler if one is set, else ``NULL``. + * + * :param pkgconf_client_t* client: The client object to get the unveil handler from. + * :return: a function pointer to the error handler or ``NULL`` + */ +pkgconf_unveil_handler_func_t +pkgconf_client_get_unveil_handler(const pkgconf_client_t *client) +{ + return client->unveil_handler; +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler) + * + * Sets an unveil handler on a client object or uninstalls one if set to ``NULL``. + * + * :param pkgconf_client_t* client: The client object to set the error handler on. + * :param pkgconf_unveil_handler_func_t unveil_handler: The unveil handler to set. + * :return: nothing + */ +void +pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler) +{ + client->unveil_handler = unveil_handler; + + if (client->unveil_handler == NULL) + { + PKGCONF_TRACE(client, "installing default unveil handler"); + client->unveil_handler = default_unveil_handler; + } +} + +#ifndef PKGCONF_LITE +/* + * !doc + * + * .. c:function:: pkgconf_client_get_trace_handler(const pkgconf_client_t *client) + * + * Returns the error handler if one is set, else ``NULL``. + * + * :param pkgconf_client_t* client: The client object to get the error handler from. + * :return: a function pointer to the error handler or ``NULL`` + */ +pkgconf_error_handler_func_t +pkgconf_client_get_trace_handler(const pkgconf_client_t *client) +{ + return client->trace_handler; +} + +/* + * !doc + * + * .. c:function:: pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data) + * + * Sets a warn handler on a client object or uninstalls one if set to ``NULL``. + * + * :param pkgconf_client_t* client: The client object to set the error handler on. + * :param pkgconf_error_handler_func_t trace_handler: The error handler to set. + * :param void* trace_handler_data: Optional data to associate with the error handler. + * :return: nothing + */ +void +pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data) +{ + client->trace_handler = trace_handler; + client->trace_handler_data = trace_handler_data; + + if (client->trace_handler == NULL) + { + client->trace_handler = pkgconf_default_error_handler; + PKGCONF_TRACE(client, "installing default trace handler"); + } +} +#endif + +/* + * !doc + * + * .. c:function:: bool pkgconf_client_preload_path(pkgconf_client_t *client, const char *path) + * + * Loads a pkg-config file into the preloaded packages set. + * + * :param pkgconf_client_t* client: The client object for preloading. + * :param char* path: The path to the pkg-config file to preload. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_client_preload_path(pkgconf_client_t *client, const char *path) +{ + pkgconf_pkg_t *pkg = pkgconf_pkg_new_from_path(client, path, PKGCONF_PKG_PROPF_PRELOADED); + if (pkg == NULL) + return false; + + pkgconf_pkg_ref(client, pkg); + pkgconf_node_insert_tail(&pkg->preload_node, pkg, &client->preloaded_pkgs); + + return true; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env) + * + * Loads zero or more pkg-config files specified in the given environmental + * variable. + * + * :param pkgconf_client_t* client: The client object for preloading. + * :param char* environ: The environment variable to use for preloading. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env) +{ + const char *data; + pkgconf_list_t pathlist = PKGCONF_LIST_INITIALIZER; + pkgconf_node_t *n; + bool ret; + + data = getenv(env); + if (data == NULL) + return true; + + pkgconf_path_split(data, &pathlist, true); + + PKGCONF_FOREACH_LIST_ENTRY(pathlist.head, n) + { + pkgconf_path_t *pn = n->data; + + ret = pkgconf_client_preload_path(client, pn->path); + if (!ret) + break; + } + + pkgconf_path_free(&pathlist); + + return ret; +} diff --git a/libpkgconf/config.h.meson b/libpkgconf/config.h.meson new file mode 100644 index 00000000000..2ea7db2e20d --- /dev/null +++ b/libpkgconf/config.h.meson @@ -0,0 +1,79 @@ +/* libpkgconf/config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the `strlcat' function. */ +#mesondefine HAVE_STRLCAT + +/* Define to 1 if you have the `strlcpy' function. */ +#mesondefine HAVE_STRLCPY + +/* Define to 1 if you have the `strndup' function. */ +#mesondefine HAVE_STRNDUP + +/* Define to 1 if you have the `reallocarray' function. */ +#mesondefine HAVE_REALLOCARRAY + +/* Define to 1 if you have the `strlcat' function. */ +#mesondefine HAVE_DECL_STRLCAT + +/* Define to 1 if you have the `strlcpy' function. */ +#mesondefine HAVE_DECL_STRLCPY + +/* Define to 1 if you have the `strndup' function. */ +#mesondefine HAVE_DECL_STRNDUP + +/* Define to 1 if you have the `reallocarray' function. */ +#mesondefine HAVE_DECL_REALLOCARRAY + +/* Define to 1 if you have the `pledge' function. */ +#mesondefine HAVE_DECL_PLEDGE + +/* Define to 1 if you have the `unveil' function. */ +#mesondefine HAVE_DECL_UNVEIL + +/* Name of package */ +#mesondefine PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#mesondefine PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#mesondefine PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#mesondefine PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#mesondefine PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#mesondefine PACKAGE_URL + +/* Define to the version of this package. */ +#mesondefine PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#mesondefine STDC_HEADERS + +/* Version number of package */ +#mesondefine VERSION + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +#mesondefine _FILE_OFFSET_BITS + +/* Define for large files, on AIX-style hosts. */ +#mesondefine _LARGE_FILES + +#mesondefine PKG_DEFAULT_PATH +#mesondefine SYSTEM_INCLUDEDIR +#mesondefine SYSTEM_LIBDIR +#mesondefine PERSONALITY_PATH + +/* Enable Solaris extensions. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif diff --git a/libpkgconf/dependency.c b/libpkgconf/dependency.c new file mode 100644 index 00000000000..9b64809a411 --- /dev/null +++ b/libpkgconf/dependency.c @@ -0,0 +1,506 @@ +/* + * dependency.c + * dependency parsing and management + * + * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `dependency` module + * ============================== + * + * The `dependency` module provides support for building `dependency lists` (the basic component of the overall `dependency graph`) and + * `dependency nodes` which store dependency information. + */ + +typedef enum { + OUTSIDE_MODULE = 0, + INSIDE_MODULE_NAME = 1, + BEFORE_OPERATOR = 2, + INSIDE_OPERATOR = 3, + AFTER_OPERATOR = 4, + INSIDE_VERSION = 5 +} parse_state_t; + +#define DEBUG_PARSE 0 + +static const char * +dependency_to_str(const pkgconf_dependency_t *dep, char *buf, size_t buflen) +{ + pkgconf_strlcpy(buf, dep->package, buflen); + if (dep->version != NULL) + { + pkgconf_strlcat(buf, " ", buflen); + pkgconf_strlcat(buf, pkgconf_pkg_get_comparator(dep), buflen); + pkgconf_strlcat(buf, " ", buflen); + pkgconf_strlcat(buf, dep->version, buflen); + } + + return buf; +} + +/* find a colliding dependency that is coloured differently */ +static inline pkgconf_dependency_t * +find_colliding_dependency(const pkgconf_dependency_t *dep, const pkgconf_list_t *list) +{ + const pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, n) + { + pkgconf_dependency_t *dep2 = n->data; + + if (strcmp(dep->package, dep2->package)) + continue; + + if (dep->flags != dep2->flags) + return dep2; + } + + return NULL; +} + +static inline pkgconf_dependency_t * +add_or_replace_dependency_node(pkgconf_client_t *client, pkgconf_dependency_t *dep, pkgconf_list_t *list) +{ + char depbuf[PKGCONF_ITEM_SIZE]; + pkgconf_dependency_t *dep2 = find_colliding_dependency(dep, list); + + /* there is already a node in the graph which describes this dependency */ + if (dep2 != NULL) + { + char depbuf2[PKGCONF_ITEM_SIZE]; + + PKGCONF_TRACE(client, "dependency collision: [%s/%x] -- [%s/%x]", + dependency_to_str(dep, depbuf, sizeof depbuf), dep->flags, + dependency_to_str(dep2, depbuf2, sizeof depbuf2), dep2->flags); + + /* prefer the uncoloured node, either dep or dep2 */ + if (dep->flags && dep2->flags == 0) + { + PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depbuf, dep); + + pkgconf_dependency_unref(dep->owner, dep); + return NULL; + } + else if (dep2->flags && dep->flags == 0) + { + PKGCONF_TRACE(client, "dropping dependency [%s]@%p because of collision", depbuf2, dep2); + + pkgconf_node_delete(&dep2->iter, list); + pkgconf_dependency_unref(dep2->owner, dep2); + } + else + /* If both dependencies have equal strength, we keep both, because of situations like: + * Requires: foo > 1, foo < 3 + * + * If the situation is that both dependencies are literally equal, it is still harmless because + * fragment deduplication will handle the excessive fragments. + */ + PKGCONF_TRACE(client, "keeping both dependencies (harmless)"); + } + + PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", dependency_to_str(dep, depbuf, sizeof depbuf), list, dep->flags); + pkgconf_node_insert_tail(&dep->iter, pkgconf_dependency_ref(dep->owner, dep), list); + + /* This dependency is intentionally unowned. + * + * Internally we have no use for the returned type, and usually just + * discard it. However, there is a publig pkgconf_dependency_add + * function, which references this return value before returning it, + * giving ownership at that point. + */ + return dep; +} + +static inline pkgconf_dependency_t * +pkgconf_dependency_addraw(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, size_t package_sz, const char *version, size_t version_sz, pkgconf_pkg_comparator_t compare, unsigned int flags) +{ + pkgconf_dependency_t *dep; + + dep = calloc(1, sizeof(pkgconf_dependency_t)); + if (dep == NULL) + return NULL; + + dep->package = pkgconf_strndup(package, package_sz); + + if (version_sz != 0) + dep->version = pkgconf_strndup(version, version_sz); + + dep->compare = compare; + dep->flags = flags; + dep->owner = client; + dep->refcount = 0; + + return add_or_replace_dependency_node(client, dep, list); +} + +/* + * !doc + * + * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare) + * + * Adds a parsed dependency to a dependency list as a dependency node. + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_list_t* list: The dependency list to add a dependency node to. + * :param char* package: The package `atom` to set on the dependency node. + * :param char* version: The package `version` to set on the dependency node. + * :param pkgconf_pkg_comparator_t compare: The comparison operator to set on the dependency node. + * :param uint flags: Any flags to attach to the dependency node. + * :return: A dependency node. + * :rtype: pkgconf_dependency_t * + */ +pkgconf_dependency_t * +pkgconf_dependency_add(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare, unsigned int flags) +{ + pkgconf_dependency_t *dep; + dep = pkgconf_dependency_addraw(client, list, package, strlen(package), version, + version != NULL ? strlen(version) : 0, compare, flags); + return pkgconf_dependency_ref(dep->owner, dep); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail) + * + * Adds a dependency node to a pre-existing dependency list. + * + * :param pkgconf_list_t* list: The dependency list to add a dependency node to. + * :param pkgconf_dependency_t* tail: The dependency node to add to the tail of the dependency list. + * :return: nothing + */ +void +pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail) +{ + pkgconf_node_insert_tail(&tail->iter, tail, list); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_free_one(pkgconf_dependency_t *dep) + * + * Frees a dependency node. + * + * :param pkgconf_dependency_t* dep: The dependency node to free. + * :return: nothing + */ +void +pkgconf_dependency_free_one(pkgconf_dependency_t *dep) +{ + if (dep->match != NULL) + pkgconf_pkg_unref(dep->match->owner, dep->match); + + if (dep->package != NULL) + free(dep->package); + + if (dep->version != NULL) + free(dep->version); + + free(dep); +} + +/* + * !doc + * + * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_ref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) + * + * Increases a dependency node's refcount. + * + * :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. + * :param pkgconf_dependency_t* dep: The dependency to increase the refcount of. + * :return: the dependency node on success, else NULL + */ +pkgconf_dependency_t * +pkgconf_dependency_ref(pkgconf_client_t *client, pkgconf_dependency_t *dep) +{ + if (client != dep->owner) + return NULL; + + dep->refcount++; + PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount); + return dep; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_unref(pkgconf_client_t *owner, pkgconf_dependency_t *dep) + * + * Decreases a dependency node's refcount and frees it if necessary. + * + * :param pkgconf_client_t* owner: The client object which owns the memory of this dependency node. + * :param pkgconf_dependency_t* dep: The dependency to decrease the refcount of. + * :return: nothing + */ +void +pkgconf_dependency_unref(pkgconf_client_t *client, pkgconf_dependency_t *dep) +{ + if (client != dep->owner) + return; + + --dep->refcount; + PKGCONF_TRACE(client, "%s refcount@%p: %d", dep->package, dep, dep->refcount); + + if (dep->refcount <= 0) + pkgconf_dependency_free_one(dep); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_free(pkgconf_list_t *list) + * + * Release a dependency list and its child dependency nodes. + * + * :param pkgconf_list_t* list: The dependency list to release. + * :return: nothing + */ +void +pkgconf_dependency_free(pkgconf_list_t *list) +{ + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + { + pkgconf_dependency_t *dep = node->data; + + pkgconf_node_delete(&dep->iter, list); + pkgconf_dependency_unref(dep->owner, dep); + } + + pkgconf_list_zero(list); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends) + * + * Parse a dependency declaration into a dependency list. + * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed + * into ``, zlib``. + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_list_t* deplist_head: The dependency list to populate with dependency nodes. + * :param char* depends: The dependency data to parse. + * :param uint flags: Any flags to attach to the dependency nodes. + * :return: nothing + */ +void +pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags) +{ + parse_state_t state = OUTSIDE_MODULE; + pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY; + char cmpname[PKGCONF_ITEM_SIZE]; + size_t package_sz = 0, version_sz = 0, buf_sz = 0; + char *buf; + char *start = NULL; + char *ptr = NULL; + char *vstart = NULL; + char *package = NULL, *version = NULL; + char *cnameptr = cmpname; + char *cnameend = cmpname + PKGCONF_ITEM_SIZE - 1; + + if (!*depends) + return; + + memset(cmpname, '\0', sizeof cmpname); + + buf_sz = strlen(depends) * 2; + buf = calloc(1, buf_sz); + if (buf == NULL) + return; + + pkgconf_strlcpy(buf, depends, buf_sz); + pkgconf_strlcat(buf, " ", buf_sz); + + start = ptr = buf; + + while (*ptr) + { + switch (state) + { + case OUTSIDE_MODULE: + if (!PKGCONF_IS_MODULE_SEPARATOR(*ptr)) + state = INSIDE_MODULE_NAME; + + break; + + case INSIDE_MODULE_NAME: + if (isspace((unsigned char)*ptr)) + { + const char *sptr = ptr; + + while (*sptr && isspace((unsigned char)*sptr)) + sptr++; + + if (*sptr == '\0') + state = OUTSIDE_MODULE; + else if (PKGCONF_IS_MODULE_SEPARATOR(*sptr)) + state = OUTSIDE_MODULE; + else if (PKGCONF_IS_OPERATOR_CHAR(*sptr)) + state = BEFORE_OPERATOR; + else + state = OUTSIDE_MODULE; + } + else if (PKGCONF_IS_MODULE_SEPARATOR(*ptr)) + state = OUTSIDE_MODULE; + else if (*(ptr + 1) == '\0') + { + ptr++; + state = OUTSIDE_MODULE; + } + + if (state != INSIDE_MODULE_NAME && start != ptr) + { + char *iter = start; + + while (PKGCONF_IS_MODULE_SEPARATOR(*iter)) + iter++; + + package = iter; + package_sz = ptr - iter; + start = ptr; + } + + if (state == OUTSIDE_MODULE) + { + pkgconf_dependency_addraw(client, deplist_head, package, package_sz, NULL, 0, compare, flags); + + compare = PKGCONF_CMP_ANY; + package_sz = 0; + } + + break; + + case BEFORE_OPERATOR: + if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) + { + state = INSIDE_OPERATOR; + if (cnameptr < cnameend) + *cnameptr++ = *ptr; + } + + break; + + case INSIDE_OPERATOR: + if (PKGCONF_IS_OPERATOR_CHAR(*ptr)) + { + if (cnameptr < cnameend) + *cnameptr++ = *ptr; + break; + } + + state = AFTER_OPERATOR; + compare = pkgconf_pkg_comparator_lookup_by_name(cmpname); + // fallthrough + + case AFTER_OPERATOR: + if (!isspace((unsigned char)*ptr)) + { + vstart = ptr; + state = INSIDE_VERSION; + } + break; + + case INSIDE_VERSION: + if (PKGCONF_IS_MODULE_SEPARATOR(*ptr) || *(ptr + 1) == '\0') + { + version = vstart; + version_sz = ptr - vstart; + state = OUTSIDE_MODULE; + + pkgconf_dependency_addraw(client, deplist_head, package, package_sz, version, version_sz, compare, flags); + + compare = PKGCONF_CMP_ANY; + cnameptr = cmpname; + memset(cmpname, 0, sizeof cmpname); + package_sz = 0; + } + + if (state == OUTSIDE_MODULE) + start = ptr; + break; + } + + ptr++; + } + + free(buf); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_dependency_parse(const pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends) + * + * Preprocess dependency data and then process that dependency declaration into a dependency list. + * Commas are counted as whitespace to allow for constructs such as ``@SUBSTVAR@, zlib`` being processed + * into ``, zlib``. + * + * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to. + * :param pkgconf_pkg_t* pkg: The package object that owns this dependency list. + * :param pkgconf_list_t* deplist: The dependency list to populate with dependency nodes. + * :param char* depends: The dependency data to parse. + * :param uint flags: Any flags to attach to the dependency nodes. + * :return: nothing + */ +void +pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags) +{ + char *kvdepends = pkgconf_tuple_parse(client, &pkg->vars, depends, pkg->flags); + + pkgconf_dependency_parse_str(client, deplist, kvdepends, flags); + free(kvdepends); +} + +/* + * !doc + * + * .. c:function:: pkgconf_dependency_t *pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep) + * + * Copies a dependency node to a new one. + * + * :param pkgconf_client_t* client: The client object that will own this dependency. + * :param pkgconf_dependency_t* dep: The dependency node to copy. + * :return: a pointer to a new dependency node, else NULL + */ +pkgconf_dependency_t * +pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep) +{ + pkgconf_dependency_t *new_dep; + + new_dep = calloc(1, sizeof(pkgconf_dependency_t)); + if (new_dep == NULL) + return NULL; + + new_dep->package = strdup(dep->package); + + if (dep->version != NULL) + new_dep->version = strdup(dep->version); + + new_dep->compare = dep->compare; + new_dep->flags = dep->flags; + new_dep->owner = client; + new_dep->refcount = 0; + + if (dep->match != NULL) + new_dep->match = pkgconf_pkg_ref(client, dep->match); + + return pkgconf_dependency_ref(client, new_dep); +} diff --git a/libpkgconf/fileio.c b/libpkgconf/fileio.c new file mode 100644 index 00000000000..d4f001b9c3e --- /dev/null +++ b/libpkgconf/fileio.c @@ -0,0 +1,113 @@ +/* + * fileio.c + * File reading utilities + * + * Copyright (c) 2012, 2025 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +bool +pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream) +{ + bool quoted = false; + int c = '\0', c2; + + while ((c = getc(stream)) != EOF) + { + if (c == '\\' && !quoted) + { + quoted = true; + continue; + } + else if (c == '#') + { + if (!quoted) { + /* Skip the rest of the line */ + do { + c = getc(stream); + } while (c != '\n' && c != EOF); + pkgconf_buffer_push_byte(buffer, c); + break; + } + else + pkgconf_buffer_push_byte(buffer, c); + + quoted = false; + continue; + } + else if (c == '\n') + { + if (quoted) + { + /* Trim spaces */ + do { + c2 = getc(stream); + } while (c2 == '\t' || c2 == ' '); + + ungetc(c2, stream); + + quoted = false; + continue; + } + else + { + pkgconf_buffer_push_byte(buffer, c); + } + + break; + } + else if (c == '\r') + { + pkgconf_buffer_push_byte(buffer, '\n'); + + if ((c2 = getc(stream)) == '\n') + { + if (quoted) + { + quoted = false; + continue; + } + + break; + } + + ungetc(c2, stream); + + if (quoted) + { + quoted = false; + continue; + } + + break; + } + else + { + if (quoted) { + pkgconf_buffer_push_byte(buffer, '\\'); + quoted = false; + } + pkgconf_buffer_push_byte(buffer, c); + } + + } + + /* Remove newline character. */ + if (pkgconf_buffer_lastc(buffer) == '\n') + pkgconf_buffer_trim_byte(buffer); + + if (pkgconf_buffer_lastc(buffer) == '\r') + pkgconf_buffer_trim_byte(buffer); + + return !(c == EOF || ferror(stream)); +} diff --git a/libpkgconf/fragment.c b/libpkgconf/fragment.c new file mode 100644 index 00000000000..2b6109039b4 --- /dev/null +++ b/libpkgconf/fragment.c @@ -0,0 +1,803 @@ +/* + * fragment.c + * Management of fragment lists. + * + * Copyright (c) 2012, 2013, 2014 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `fragment` module + * ============================ + * + * The `fragment` module provides low-level management and rendering of fragment lists. A + * `fragment list` contains various `fragments` of text (such as ``-I /usr/include``) in a matter + * which is composable, mergeable and reorderable. + */ + +struct pkgconf_fragment_check { + char *token; + size_t len; +}; + +static inline bool +pkgconf_fragment_is_unmergeable(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-framework", 10}, + {"-isystem", 8}, + {"-idirafter", 10}, + {"-pthread", 8}, + {"-Wa,", 4}, + {"-Wl,", 4}, + {"-Wp,", 4}, + {"-trigraphs", 10}, + {"-pedantic", 9}, + {"-ansi", 5}, + {"-std=", 5}, + {"-stdlib=", 8}, + {"-include", 8}, + {"-nostdinc", 9}, + {"-nostdlibinc", 12}, + {"-nobuiltininc", 13}, + {"-nodefaultlibs", 14}, + }; + + if (*string != '-') + return true; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return true; + + /* only one pair of {-flag, arg} may be merged together */ + if (strchr(string, ' ') != NULL) + return false; + + return false; +} + +static inline bool +pkgconf_fragment_should_munge(const char *string, const char *sysroot_dir) +{ + if (*string != '/') + return false; + + if (sysroot_dir != NULL && strncmp(sysroot_dir, string, strlen(sysroot_dir))) + return true; + + return false; +} + +static inline bool +pkgconf_fragment_is_groupable(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-Wl,--start-group", 17}, + {"-framework", 10}, + {"-isystem", 8}, + {"-idirafter", 10}, + {"-include", 8}, + }; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return true; + + return false; +} + +static inline bool +pkgconf_fragment_is_terminus(const char *string) +{ + static const struct pkgconf_fragment_check check_fragments[] = { + {"-Wl,--end-group", 15}, + }; + + for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++) + if (!strncmp(string, check_fragments[i].token, check_fragments[i].len)) + return true; + + return false; +} + +static inline bool +pkgconf_fragment_is_special(const char *string) +{ + if (*string != '-') + return true; + + if (!strncmp(string, "-lib:", 5)) + return true; + + return pkgconf_fragment_is_unmergeable(string); +} + +static inline void +pkgconf_fragment_munge(const pkgconf_client_t *client, char *buf, size_t buflen, const char *source, const char *sysroot_dir, unsigned int flags) +{ + *buf = '\0'; + + if (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES)) + { + if (sysroot_dir == NULL) + sysroot_dir = pkgconf_tuple_find_global(client, "pc_sysrootdir"); + + if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir)) + pkgconf_strlcat(buf, sysroot_dir, buflen); + } + + pkgconf_strlcat(buf, source, buflen); + + if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS)) + pkgconf_path_relocate(buf, buflen); +} + +static inline char * +pkgconf_fragment_copy_munged(const pkgconf_client_t *client, const char *source, unsigned int flags) +{ + char mungebuf[PKGCONF_ITEM_SIZE]; + pkgconf_fragment_munge(client, mungebuf, sizeof mungebuf, source, client->sysroot_dir, flags); + return strdup(mungebuf); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail) + * + * Adds a `fragment` of text to a `fragment list` directly without interpreting it. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The fragment list. + * :param char type: The type of the fragment. + * :param char* data: The data of the fragment. + * :param bool tail: Whether to place the fragment at the beginning of the list or the end. + * :return: nothing + */ +void +pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail) +{ + pkgconf_fragment_t *frag; + + frag = calloc(1, sizeof(pkgconf_fragment_t)); + frag->type = type; + frag->data = pkgconf_fragment_copy_munged(client, data, 0); + + if (tail) + { + pkgconf_node_insert_tail(&frag->iter, frag, list); + return; + } + + pkgconf_node_insert(&frag->iter, frag, list); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) + * + * Adds a `fragment` of text to a `fragment list`, possibly modifying the fragment if a sysroot is set. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The fragment list. + * :param char* string: The string of text to add as a fragment to the fragment list. + * :param uint flags: Parsing-related flags for the package. + * :return: nothing + */ +void +pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags) +{ + pkgconf_list_t *target = list; + pkgconf_fragment_t *frag; + + if (*string == '\0') + return; + + if (list->tail != NULL && list->tail->data != NULL && + !(client->flags & PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS)) + { + pkgconf_fragment_t *parent = list->tail->data; + + /* only attempt to merge 'special' fragments together */ + if (!parent->type && parent->data != NULL && + pkgconf_fragment_is_unmergeable(parent->data) && + !(parent->flags & PKGCONF_PKG_FRAGF_TERMINATED)) + { + if (pkgconf_fragment_is_groupable(parent->data)) + target = &parent->children; + + if (pkgconf_fragment_is_terminus(string)) + parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED; + + PKGCONF_TRACE(client, "adding fragment as child to list @%p", target); + } + } + + frag = calloc(1, sizeof(pkgconf_fragment_t)); + if (frag == NULL) + { + PKGCONF_TRACE(client, "failed to add new fragment due to allocation failure to list @%p", target); + return; + } + + if (strlen(string) > 1 && !pkgconf_fragment_is_special(string)) + { + frag->type = *(string + 1); + frag->data = pkgconf_fragment_copy_munged(client, string + 2, flags); + + PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list); + } + else + { + frag->type = 0; + frag->data = pkgconf_fragment_copy_munged(client, string, flags); + + PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target); + } + + pkgconf_node_insert_tail(&frag->iter, frag, target); +} + +static inline pkgconf_fragment_t * +pkgconf_fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_t *base) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY_REVERSE(list->tail, node) + { + pkgconf_fragment_t *frag = node->data; + + if (base->type != frag->type) + continue; + + if (!strcmp(base->data, frag->data)) + return frag; + } + + return NULL; +} + +static inline bool +pkgconf_fragment_can_merge_back(const pkgconf_fragment_t *base, unsigned int flags, bool is_private) +{ + (void) flags; + + if (base->type == 'l') + { + if (is_private) + return false; + + return true; + } + + if (base->type == 'F') + return false; + if (base->type == 'L') + return false; + if (base->type == 'I') + return false; + + return true; +} + +static inline bool +pkgconf_fragment_can_merge(const pkgconf_fragment_t *base, unsigned int flags, bool is_private) +{ + (void) flags; + + if (is_private) + return false; + + if (base->children.head != NULL) + return false; + + return pkgconf_fragment_is_unmergeable(base->data); +} + +static inline pkgconf_fragment_t * +pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_t *base, unsigned int flags, bool is_private) +{ + if (!pkgconf_fragment_can_merge_back(base, flags, is_private)) + return NULL; + + if (!pkgconf_fragment_can_merge(base, flags, is_private)) + return NULL; + + return pkgconf_fragment_lookup(list, base); +} + +static inline bool +pkgconf_fragment_should_merge(const pkgconf_fragment_t *base) +{ + const pkgconf_fragment_t *parent; + + /* if we are the first fragment, that means the next fragment is the same, so it's always safe. */ + if (base->iter.prev == NULL) + return true; + + /* this really shouldn't ever happen, but handle it */ + parent = base->iter.prev->data; + if (parent == NULL) + return true; + + switch (parent->type) + { + case 'l': + case 'L': + case 'I': + return true; + default: + return !base->type || parent->type == base->type; + } +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag) + * + * Checks if a `fragment` contains a `system path`. System paths are detected at compile time and optionally overridden by + * the ``PKG_CONFIG_SYSTEM_INCLUDE_PATH`` and ``PKG_CONFIG_SYSTEM_LIBRARY_PATH`` environment variables. + * + * :param pkgconf_client_t* client: The pkgconf client object the fragment belongs to. + * :param pkgconf_fragment_t* frag: The fragment being checked. + * :return: true if the fragment contains a system path, else false + * :rtype: bool + */ +bool +pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag) +{ + const pkgconf_list_t *check_paths = NULL; + + switch (frag->type) + { + case 'L': + check_paths = &client->filter_libdirs; + break; + case 'I': + check_paths = &client->filter_includedirs; + break; + default: + return false; + } + + return pkgconf_path_match_list(frag->data, check_paths); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private) + * + * Copies a `fragment` to another `fragment list`, possibly removing a previous copy of the `fragment` + * in a process known as `mergeback`. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The list the fragment is being added to. + * :param pkgconf_fragment_t* base: The fragment being copied. + * :param bool is_private: Whether the fragment list is a `private` fragment list (static linking). + * :return: nothing + */ +void +pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private) +{ + pkgconf_fragment_t *frag; + + if ((frag = pkgconf_fragment_exists(list, base, client->flags, is_private)) != NULL) + { + if (pkgconf_fragment_should_merge(frag)) + pkgconf_fragment_delete(list, frag); + } + else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (pkgconf_fragment_lookup(list, base) != NULL)) + return; + + frag = calloc(1, sizeof(pkgconf_fragment_t)); + + frag->type = base->type; + pkgconf_fragment_copy_list(client, &frag->children, &base->children); + if (base->data != NULL) + frag->data = strdup(base->data); + + pkgconf_node_insert_tail(&frag->iter, frag, list); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) + * + * Copies a `fragment list` to another `fragment list`, possibly removing a previous copy of the fragments + * in a process known as `mergeback`. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The list the fragments are being added to. + * :param pkgconf_list_t* base: The list the fragments are being copied from. + * :return: nothing + */ +void +pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(base->head, node) + { + pkgconf_fragment_t *frag = node->data; + + pkgconf_fragment_copy(client, list, frag, true); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func) + * + * Copies a `fragment list` to another `fragment list` which match a user-specified filtering function. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* dest: The destination list. + * :param pkgconf_list_t* src: The source list. + * :param pkgconf_fragment_filter_func_t filter_func: The filter function to use. + * :param void* data: Optional data to pass to the filter function. + * :return: nothing + */ +void +pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(src->head, node) + { + pkgconf_fragment_t *frag = node->data; + + if (filter_func(client, frag, data)) + pkgconf_fragment_copy(client, dest, frag, true); + } +} + +static inline char * +fragment_quote(const pkgconf_fragment_t *frag) +{ + const char *src = frag->data; + ssize_t outlen = strlen(src) + 10; + char *out, *dst; + + if (frag->data == NULL) + return NULL; + + out = dst = calloc(1, outlen); + if (out == NULL) + return NULL; + + for (; *src; src++) + { + if (((*src < ' ') || + (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') || + (*src > '$' && *src < '(') || + (*src > ')' && *src < '+') || + (*src > ':' && *src < '=') || + (*src > '=' && *src < '@') || + (*src > 'Z' && *src < '\\') || +#ifndef _WIN32 + (*src == '\\') || +#endif + (*src > '\\' && *src < '^') || + (*src == '`') || + (*src > 'z' && *src < '~') || + (*src > '~'))) + *dst++ = '\\'; + + *dst++ = *src; + + if ((ptrdiff_t)(dst - out) + 2 > outlen) + { + ptrdiff_t offset = dst - out; + outlen *= 2; + + char *newout = realloc(out, outlen); + if (newout == NULL) + { + free(out); + return NULL; + } + + out = newout; + dst = out + offset; + } + } + + *dst = 0; + return out; +} + +static inline size_t +pkgconf_fragment_len(const pkgconf_fragment_t *frag) +{ + size_t len = 1; + + if (frag->type) + len += 2; + + if (frag->data != NULL) + { + pkgconf_node_t *iter; + + char *quoted = fragment_quote(frag); + len += strlen(quoted); + free(quoted); + + PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter) + { + const pkgconf_fragment_t *child_frag = iter->data; + len += pkgconf_fragment_len(child_frag) + 1; + } + } + + return len; +} + +static size_t +fragment_render_len(const pkgconf_list_t *list, bool escape) +{ + (void) escape; + + size_t out = 1; /* trailing nul */ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + const pkgconf_fragment_t *frag = node->data; + out += pkgconf_fragment_len(frag); + } + + return out; +} + +static inline size_t +fragment_render_item(const pkgconf_fragment_t *frag, char *bptr, size_t bufremain) +{ + const pkgconf_node_t *iter; + char *base = bptr; + + char *quoted = fragment_quote(frag); + if (quoted == NULL) + return 0; + + if (strlen(quoted) > bufremain) + { + free(quoted); + return 0; + } + + if (frag->type) + { + *bptr++ = '-'; + *bptr++ = frag->type; + } + + if (quoted != NULL) + { + bptr += pkgconf_strlcpy(bptr, quoted, bufremain - (bptr - base)); + free(quoted); + } + + PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter) + { + const pkgconf_fragment_t *child_frag = iter->data; + + *bptr++ = ' '; + bptr += fragment_render_item(child_frag, bptr, bufremain - (bptr - base)); + } + + return bptr - base; +} + +static void +fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape) +{ + (void) escape; + + pkgconf_node_t *node; + char *bptr = buf; + + memset(buf, 0, buflen); + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + const pkgconf_fragment_t *frag = node->data; + size_t buf_remaining = buflen - (bptr - buf); + size_t written = fragment_render_item(frag, bptr, buf_remaining); + + bptr += written; + + if (node->next != NULL) + *bptr++ = ' '; + } +} + +static const pkgconf_fragment_render_ops_t default_render_ops = { + .render_len = fragment_render_len, + .render_buf = fragment_render_buf +}; + +/* + * !doc + * + * .. c:function:: size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) + * + * Calculates the required memory to store a `fragment list` when rendered as a string. + * + * :param pkgconf_list_t* list: The `fragment list` being rendered. + * :param bool escape: Whether or not to escape special shell characters (deprecated). + * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + * :return: the amount of bytes required to represent the `fragment list` when rendered + * :rtype: size_t + */ +size_t +pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) +{ + (void) escape; + + ops = ops != NULL ? ops : &default_render_ops; + return ops->render_len(list, true); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops) + * + * Renders a `fragment list` into a buffer. + * + * :param pkgconf_list_t* list: The `fragment list` being rendered. + * :param char* buf: The buffer to render the fragment list into. + * :param size_t buflen: The length of the buffer. + * :param bool escape: Whether or not to escape special shell characters (deprecated). + * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + * :return: nothing + */ +void +pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape, const pkgconf_fragment_render_ops_t *ops) +{ + (void) escape; + + ops = ops != NULL ? ops : &default_render_ops; + ops->render_buf(list, buf, buflen, true); +} + +/* + * !doc + * + * .. c:function:: char *pkgconf_fragment_render(const pkgconf_list_t *list) + * + * Allocate memory and render a `fragment list` into it. + * + * :param pkgconf_list_t* list: The `fragment list` being rendered. + * :param bool escape: Whether or not to escape special shell characters (deprecated). + * :param pkgconf_fragment_render_ops_t* ops: An optional ops structure to use for custom renderers, else ``NULL``. + * :return: An allocated string containing the rendered `fragment list`. + * :rtype: char * + */ +char * +pkgconf_fragment_render(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops) +{ + (void) escape; + + size_t buflen = pkgconf_fragment_render_len(list, true, ops); + char *buf = calloc(1, buflen); + + pkgconf_fragment_render_buf(list, buf, buflen, true, ops); + + return buf; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node) + * + * Delete a `fragment node` from a `fragment list`. + * + * :param pkgconf_list_t* list: The `fragment list` to delete from. + * :param pkgconf_fragment_t* node: The `fragment node` to delete. + * :return: nothing + */ +void +pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node) +{ + pkgconf_node_delete(&node->iter, list); + + free(node->data); + free(node); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_fragment_free(pkgconf_list_t *list) + * + * Delete an entire `fragment list`. + * + * :param pkgconf_list_t* list: The `fragment list` to delete. + * :return: nothing + */ +void +pkgconf_fragment_free(pkgconf_list_t *list) +{ + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + { + pkgconf_fragment_t *frag = node->data; + + pkgconf_fragment_free(&frag->children); + free(frag->data); + free(frag); + } +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value) + * + * Parse a string into a `fragment list`. + * + * :param pkgconf_client_t* client: The pkgconf client being accessed. + * :param pkgconf_list_t* list: The `fragment list` to add the fragment entries to. + * :param pkgconf_list_t* vars: A list of variables to use for variable substitution. + * :param uint flags: Any parsing flags to be aware of. + * :param char* value: The string to parse into fragments. + * :return: true on success, false on parse error + */ +bool +pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags) +{ + int i, ret, argc; + char **argv; + char *repstr = pkgconf_tuple_parse(client, vars, value, flags); + + PKGCONF_TRACE(client, "post-subst: [%s] -> [%s]", value, repstr); + + ret = pkgconf_argv_split(repstr, &argc, &argv); + if (ret < 0) + { + PKGCONF_TRACE(client, "unable to parse fragment string [%s]", repstr); + free(repstr); + return false; + } + + for (i = 0; i < argc; i++) + { + PKGCONF_TRACE(client, "processing %s", argv[i]); + + if (argv[i] == NULL) + { + PKGCONF_TRACE(client, "parsed fragment string is inconsistent: argc = %d while argv[%d] == NULL", argc, i); + pkgconf_argv_free(argv); + free(repstr); + return false; + } + + pkgconf_fragment_add(client, list, argv[i], flags); + } + + pkgconf_argv_free(argv); + free(repstr); + + return true; +} diff --git a/libpkgconf/iter.h b/libpkgconf/iter.h new file mode 100644 index 00000000000..199d299f6be --- /dev/null +++ b/libpkgconf/iter.h @@ -0,0 +1,113 @@ +/* + * iter.h + * Linked lists and iterators. + * + * Copyright (c) 2013 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBPKGCONF_ITER_H +#define LIBPKGCONF_ITER_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct pkgconf_node_ pkgconf_node_t; + +struct pkgconf_node_ { + pkgconf_node_t *prev, *next; + void *data; +}; + +typedef struct { + pkgconf_node_t *head, *tail; + size_t length; +} pkgconf_list_t; + +#define PKGCONF_LIST_INITIALIZER { NULL, NULL, 0 } + +static inline void +pkgconf_list_zero(pkgconf_list_t *list) +{ + list->head = NULL; + list->tail = NULL; + list->length = 0; +} + +static inline void +pkgconf_node_insert(pkgconf_node_t *node, void *data, pkgconf_list_t *list) +{ + pkgconf_node_t *tnode; + + node->data = data; + + if (list->head == NULL) + { + list->head = node; + list->tail = node; + list->length = 1; + return; + } + + tnode = list->head; + + node->next = tnode; + tnode->prev = node; + + list->head = node; + list->length++; +} + +static inline void +pkgconf_node_insert_tail(pkgconf_node_t *node, void *data, pkgconf_list_t *list) +{ + pkgconf_node_t *tnode; + + node->data = data; + + if (list->tail == NULL) + { + list->head = node; + list->tail = node; + list->length = 1; + return; + } + + tnode = list->tail; + + node->prev = tnode; + tnode->next = node; + + list->tail = node; + list->length++; +} + +static inline void +pkgconf_node_delete(pkgconf_node_t *node, pkgconf_list_t *list) +{ + list->length--; + + if (node->prev == NULL) + list->head = node->next; + else + node->prev->next = node->next; + + if (node->next == NULL) + list->tail = node->prev; + else + node->next->prev = node->prev; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libpkgconf/libpkgconf-api.h b/libpkgconf/libpkgconf-api.h new file mode 100644 index 00000000000..a924c80ce83 --- /dev/null +++ b/libpkgconf/libpkgconf-api.h @@ -0,0 +1,19 @@ +#ifndef LIBPKGCONF_LIBPKGCONF_API_H +#define LIBPKGCONF_LIBPKGCONF_API_H + +/* Makefile.am specifies visibility using the libtool option -export-symbols-regex '^pkgconf_' + * Unfortunately, that is not available when building with meson, so use attributes instead. + */ +#if defined(PKGCONFIG_IS_STATIC) +# define PKGCONF_API +#elif defined(_WIN32) || defined(_WIN64) +# if defined(LIBPKGCONF_EXPORT) || defined(DLL_EXPORT) +# define PKGCONF_API __declspec(dllexport) +# else +# define PKGCONF_API __declspec(dllimport) +# endif +#else +# define PKGCONF_API __attribute__((visibility("default"))) +#endif + +#endif diff --git a/libpkgconf/libpkgconf.h b/libpkgconf/libpkgconf.h new file mode 100644 index 00000000000..ed0f1f996ce --- /dev/null +++ b/libpkgconf/libpkgconf.h @@ -0,0 +1,493 @@ +/* + * libpkgconf.h + * Global include file for everything in libpkgconf. + * + * Copyright (c) 2011, 2015 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBPKGCONF__LIBPKGCONF_H +#define LIBPKGCONF__LIBPKGCONF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* pkg-config uses ';' on win32 as ':' is part of path */ +#ifdef _WIN32 +#define PKG_CONFIG_PATH_SEP_S ";" +#else +#define PKG_CONFIG_PATH_SEP_S ":" +#endif + +#ifdef _WIN32 +#define PKG_DIR_SEP_S '\\' +#else +#define PKG_DIR_SEP_S '/' +#endif + +#ifdef _WIN32 +#define realpath(N,R) _fullpath((R),(N),_MAX_PATH) +#endif + +#define PKGCONF_BUFSIZE (65535) + +typedef enum { + PKGCONF_CMP_NOT_EQUAL, + PKGCONF_CMP_ANY, + PKGCONF_CMP_LESS_THAN, + PKGCONF_CMP_LESS_THAN_EQUAL, + PKGCONF_CMP_EQUAL, + PKGCONF_CMP_GREATER_THAN, + PKGCONF_CMP_GREATER_THAN_EQUAL +} pkgconf_pkg_comparator_t; + +#define PKGCONF_CMP_COUNT 7 + +typedef struct pkgconf_pkg_ pkgconf_pkg_t; +typedef struct pkgconf_dependency_ pkgconf_dependency_t; +typedef struct pkgconf_tuple_ pkgconf_tuple_t; +typedef struct pkgconf_fragment_ pkgconf_fragment_t; +typedef struct pkgconf_path_ pkgconf_path_t; +typedef struct pkgconf_client_ pkgconf_client_t; +typedef struct pkgconf_cross_personality_ pkgconf_cross_personality_t; +typedef struct pkgconf_queue_ pkgconf_queue_t; + +#define PKGCONF_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +#define PKGCONF_FOREACH_LIST_ENTRY(head, value) \ + for ((value) = (head); (value) != NULL; (value) = (value)->next) + +#define PKGCONF_FOREACH_LIST_ENTRY_SAFE(head, nextiter, value) \ + for ((value) = (head), (nextiter) = (head) != NULL ? (head)->next : NULL; (value) != NULL; (value) = (nextiter), (nextiter) = (nextiter) != NULL ? (nextiter)->next : NULL) + +#define PKGCONF_FOREACH_LIST_ENTRY_REVERSE(tail, value) \ + for ((value) = (tail); (value) != NULL; (value) = (value)->prev) + +#define LIBPKGCONF_VERSION 20501 +#define LIBPKGCONF_VERSION_STR "2.5.1" + +struct pkgconf_queue_ { + pkgconf_node_t iter; + char *package; + + unsigned int flags; +}; + +struct pkgconf_fragment_ { + pkgconf_node_t iter; + + char type; + char *data; + + pkgconf_list_t children; + unsigned int flags; +}; + +#define PKGCONF_PKG_FRAGF_TERMINATED 0x1 + +struct pkgconf_dependency_ { + pkgconf_node_t iter; + + char *package; + pkgconf_pkg_comparator_t compare; + char *version; + pkgconf_pkg_t *parent; + pkgconf_pkg_t *match; + + unsigned int flags; + + int refcount; + pkgconf_client_t *owner; +}; + +struct pkgconf_tuple_ { + pkgconf_node_t iter; + + char *key; + char *value; + + unsigned int flags; +}; + +#define PKGCONF_PKG_TUPLEF_OVERRIDE 0x1 + +struct pkgconf_path_ { + pkgconf_node_t lnode; + + char *path; + void *handle_path; + void *handle_device; + + unsigned int flags; +}; + +#define PKGCONF_PKG_PROPF_NONE 0x00 +#define PKGCONF_PKG_PROPF_STATIC 0x01 +#define PKGCONF_PKG_PROPF_CACHED 0x02 +#define PKGCONF_PKG_PROPF_UNINSTALLED 0x08 +#define PKGCONF_PKG_PROPF_VIRTUAL 0x10 +#define PKGCONF_PKG_PROPF_ANCESTOR 0x20 +#define PKGCONF_PKG_PROPF_VISITED_PRIVATE 0x40 +#define PKGCONF_PKG_PROPF_PRELOADED 0x80 + +struct pkgconf_pkg_ { + int refcount; + char *id; + char *filename; + char *realname; + char *version; + char *description; + char *url; + char *pc_filedir; + char *license; + char *maintainer; + char *copyright; + char *why; + + pkgconf_list_t libs; + pkgconf_list_t libs_private; + pkgconf_list_t cflags; + pkgconf_list_t cflags_private; + + pkgconf_list_t required; /* this used to be requires but that is now a reserved keyword */ + pkgconf_list_t requires_private; + pkgconf_list_t conflicts; + pkgconf_list_t provides; + + pkgconf_list_t vars; + + unsigned int flags; + + pkgconf_client_t *owner; + + /* these resources are owned by the package and do not need special management, + * under no circumstance attempt to allocate or free objects belonging to these pointers + */ + pkgconf_tuple_t *orig_prefix; + pkgconf_tuple_t *prefix; + + uint64_t serial; + uint64_t identifier; + + pkgconf_node_t preload_node; +}; + +typedef bool (*pkgconf_pkg_iteration_func_t)(const pkgconf_pkg_t *pkg, void *data); +typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data); +typedef bool (*pkgconf_queue_apply_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth); +typedef bool (*pkgconf_error_handler_func_t)(const char *msg, const pkgconf_client_t *client, void *data); +typedef void (*pkgconf_unveil_handler_func_t)(const pkgconf_client_t *client, const char *path, const char *permissions); + +struct pkgconf_client_ { + pkgconf_list_t dir_list; + + pkgconf_list_t filter_libdirs; + pkgconf_list_t filter_includedirs; + + pkgconf_list_t global_vars; + + void *error_handler_data; + void *warn_handler_data; + void *trace_handler_data; + + pkgconf_error_handler_func_t error_handler; + pkgconf_error_handler_func_t warn_handler; + pkgconf_error_handler_func_t trace_handler; + + FILE *auditf; + + char *sysroot_dir; + char *buildroot_dir; + + unsigned int flags; + + char *prefix_varname; + + bool already_sent_notice; + + uint64_t serial; + uint64_t identifier; + + pkgconf_pkg_t **cache_table; + size_t cache_count; + + pkgconf_unveil_handler_func_t unveil_handler; + + pkgconf_list_t preloaded_pkgs; +}; + +struct pkgconf_cross_personality_ { + char *name; + + pkgconf_list_t dir_list; + + pkgconf_list_t filter_libdirs; + pkgconf_list_t filter_includedirs; + + char *sysroot_dir; + + bool want_default_static; + bool want_default_pure; +}; + +/* client.c */ +PKGCONF_API void pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality); +PKGCONF_API pkgconf_client_t * pkgconf_client_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality); +PKGCONF_API void pkgconf_client_deinit(pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_free(pkgconf_client_t *client); +PKGCONF_API const char *pkgconf_client_get_sysroot_dir(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir); +PKGCONF_API const char *pkgconf_client_get_buildroot_dir(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_buildroot_dir(pkgconf_client_t *client, const char *buildroot_dir); +PKGCONF_API unsigned int pkgconf_client_get_flags(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_flags(pkgconf_client_t *client, unsigned int flags); +PKGCONF_API const char *pkgconf_client_get_prefix_varname(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_prefix_varname(pkgconf_client_t *client, const char *prefix_varname); +PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_warn_handler(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_warn_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t warn_handler, void *warn_handler_data); +PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_error_handler(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_error_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data); +PKGCONF_API pkgconf_error_handler_func_t pkgconf_client_get_trace_handler(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_trace_handler(pkgconf_client_t *client, pkgconf_error_handler_func_t trace_handler, void *trace_handler_data); +PKGCONF_API pkgconf_unveil_handler_func_t pkgconf_client_get_unveil_handler(const pkgconf_client_t *client); +PKGCONF_API void pkgconf_client_set_unveil_handler(pkgconf_client_t *client, pkgconf_unveil_handler_func_t unveil_handler); +PKGCONF_API void pkgconf_client_dir_list_build(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality); +PKGCONF_API bool pkgconf_client_preload_path(pkgconf_client_t *client, const char *path); +PKGCONF_API bool pkgconf_client_preload_from_environ(pkgconf_client_t *client, const char *env); + +/* personality.c */ +PKGCONF_API pkgconf_cross_personality_t *pkgconf_cross_personality_default(void); +PKGCONF_API pkgconf_cross_personality_t *pkgconf_cross_personality_find(const char *triplet); +PKGCONF_API void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *personality); + +#define PKGCONF_IS_MODULE_SEPARATOR(c) ((c) == ',' || isspace ((unsigned char)(c))) +#define PKGCONF_IS_OPERATOR_CHAR(c) ((c) == '<' || (c) == '>' || (c) == '!' || (c) == '=') + +#define PKGCONF_PKG_PKGF_NONE 0x0000 +#define PKGCONF_PKG_PKGF_SEARCH_PRIVATE 0x0001 +#define PKGCONF_PKG_PKGF_ENV_ONLY 0x0002 +#define PKGCONF_PKG_PKGF_NO_UNINSTALLED 0x0004 +#define PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL 0x0008 +#define PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS 0x0010 +#define PKGCONF_PKG_PKGF_SKIP_CONFLICTS 0x0020 +#define PKGCONF_PKG_PKGF_NO_CACHE 0x0040 +#define PKGCONF_PKG_PKGF_SKIP_ERRORS 0x0080 +#define PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE 0x0100 +#define PKGCONF_PKG_PKGF_SKIP_PROVIDES 0x0200 +#define PKGCONF_PKG_PKGF_REDEFINE_PREFIX 0x0400 +#define PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS 0x0800 +#define PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS 0x1000 +#define PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS 0x2000 +#define PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS 0x4000 +#define PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES 0x8000 +#define PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES 0x10000 + +#define PKGCONF_PKG_DEPF_INTERNAL 0x1 +#define PKGCONF_PKG_DEPF_PRIVATE 0x2 +#define PKGCONF_PKG_DEPF_QUERY 0x4 + +#define PKGCONF_PKG_ERRF_OK 0x0 +#define PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND 0x1 +#define PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH 0x2 +#define PKGCONF_PKG_ERRF_PACKAGE_CONFLICT 0x4 +#define PKGCONF_PKG_ERRF_DEPGRAPH_BREAK 0x8 + +#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +# define PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (gnu_printf, fmtarg, firstvararg))) +#elif defined(__clang__) || defined(__INTEL_COMPILER) || __GNUC__ > 2 || (_GNUC__ == 2 && __GNUC_MINOR__ >= 5) +# define PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +# define PRINTFLIKE(fmtarg, firstvararg) +#endif + +#if defined(__clang__) || defined(__INTEL_COMPILER) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +# define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define DEPRECATED __declspec(deprecated) +#else +# define DEPRECATED +#endif + +/* parser.c */ +typedef void (*pkgconf_parser_operand_func_t)(void *data, const size_t lineno, const char *key, const char *value); +typedef void (*pkgconf_parser_warn_func_t)(void *data, const char *fmt, ...); + +PKGCONF_API void pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename); + +/* pkg.c */ +PKGCONF_API bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3); +PKGCONF_API bool pkgconf_warn(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3); +PKGCONF_API bool pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...) PRINTFLIKE(5, 6); +PKGCONF_API bool pkgconf_default_error_handler(const char *msg, const pkgconf_client_t *client, void *data); + +#ifndef PKGCONF_LITE +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define PKGCONF_TRACE(client, ...) do { \ + pkgconf_trace(client, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__); \ + } while (0) +#else +#define PKGCONF_TRACE(client, ...) do { \ + pkgconf_trace(client, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } while (0) +#endif +#else +#define PKGCONF_TRACE(client, ...) +#endif + +PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_ref(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name); +PKGCONF_API unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags); +PKGCONF_API unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth); +PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags); +PKGCONF_API const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep); +PKGCONF_API unsigned int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth); +PKGCONF_API unsigned int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth); +PKGCONF_API pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name); +PKGCONF_API pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name); + +PKGCONF_API int pkgconf_compare_version(const char *a, const char *b); +PKGCONF_API pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *ptr, pkgconf_pkg_iteration_func_t func); + +/* parse.c */ +PKGCONF_API pkgconf_pkg_t *pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *path, unsigned int flags); +PKGCONF_API void pkgconf_dependency_parse_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags); +PKGCONF_API void pkgconf_dependency_parse(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist_head, const char *depends, unsigned int flags); +PKGCONF_API void pkgconf_dependency_append(pkgconf_list_t *list, pkgconf_dependency_t *tail); +PKGCONF_API void pkgconf_dependency_free(pkgconf_list_t *list); +PKGCONF_API void pkgconf_dependency_free_one(pkgconf_dependency_t *dep); +PKGCONF_API pkgconf_dependency_t *pkgconf_dependency_add(pkgconf_client_t *client, pkgconf_list_t *list, const char *package, const char *version, pkgconf_pkg_comparator_t compare, unsigned int flags); +PKGCONF_API pkgconf_dependency_t *pkgconf_dependency_ref(pkgconf_client_t *client, pkgconf_dependency_t *dep); +PKGCONF_API void pkgconf_dependency_unref(pkgconf_client_t *client, pkgconf_dependency_t *dep); +PKGCONF_API pkgconf_dependency_t *pkgconf_dependency_copy(pkgconf_client_t *client, const pkgconf_dependency_t *dep); + +/* argvsplit.c */ +PKGCONF_API int pkgconf_argv_split(const char *src, int *argc, char ***argv); +PKGCONF_API void pkgconf_argv_free(char **argv); + +/* fragment.c */ +typedef struct pkgconf_fragment_render_ops_ { + size_t (*render_len)(const pkgconf_list_t *list, bool escape); + void (*render_buf)(const pkgconf_list_t *list, char *buf, size_t len, bool escape); +} pkgconf_fragment_render_ops_t; + +typedef bool (*pkgconf_fragment_filter_func_t)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data); +PKGCONF_API bool pkgconf_fragment_parse(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags); +PKGCONF_API void pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail); +PKGCONF_API void pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags); +PKGCONF_API void pkgconf_fragment_copy(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_fragment_t *base, bool is_private); +PKGCONF_API void pkgconf_fragment_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base); +PKGCONF_API void pkgconf_fragment_delete(pkgconf_list_t *list, pkgconf_fragment_t *node); +PKGCONF_API void pkgconf_fragment_free(pkgconf_list_t *list); +PKGCONF_API void pkgconf_fragment_filter(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data); +PKGCONF_API size_t pkgconf_fragment_render_len(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops); +PKGCONF_API void pkgconf_fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t len, bool escape, const pkgconf_fragment_render_ops_t *ops); +PKGCONF_API char *pkgconf_fragment_render(const pkgconf_list_t *list, bool escape, const pkgconf_fragment_render_ops_t *ops); +PKGCONF_API bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag); + +/* tuple.c */ +PKGCONF_API pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *parent, const char *key, const char *value, bool parse, unsigned int flags); +PKGCONF_API char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key); +PKGCONF_API char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *list, const char *value, unsigned int flags); +PKGCONF_API void pkgconf_tuple_free(pkgconf_list_t *list); +PKGCONF_API void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list); +PKGCONF_API void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value); +PKGCONF_API char *pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key); +PKGCONF_API void pkgconf_tuple_free_global(pkgconf_client_t *client); +PKGCONF_API void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv); + +/* queue.c */ +PKGCONF_API void pkgconf_queue_push(pkgconf_list_t *list, const char *package); +PKGCONF_API bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list); +PKGCONF_API bool pkgconf_queue_solve(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_pkg_t *world, int maxdepth); +PKGCONF_API void pkgconf_queue_free(pkgconf_list_t *list); +PKGCONF_API bool pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data); +PKGCONF_API bool pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, int maxdepth); +PKGCONF_API void pkgconf_solution_free(pkgconf_client_t *client, pkgconf_pkg_t *world); + +/* cache.c */ +PKGCONF_API pkgconf_pkg_t *pkgconf_cache_lookup(pkgconf_client_t *client, const char *id); +PKGCONF_API void pkgconf_cache_add(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API void pkgconf_cache_remove(pkgconf_client_t *client, pkgconf_pkg_t *pkg); +PKGCONF_API void pkgconf_cache_free(pkgconf_client_t *client); + +/* audit.c */ +PKGCONF_API void pkgconf_audit_set_log(pkgconf_client_t *client, FILE *auditf); +PKGCONF_API void pkgconf_audit_log(pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3); +PKGCONF_API void pkgconf_audit_log_dependency(pkgconf_client_t *client, const pkgconf_pkg_t *dep, const pkgconf_dependency_t *depnode); + +/* path.c */ +PKGCONF_API void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter); +PKGCONF_API void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter); +PKGCONF_API size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter); +PKGCONF_API size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter); +#ifdef _WIN32 +PKGCONF_API size_t pkgconf_path_build_from_registry(/* HKEY -> HANDLE -> PVOID */ void *hKey, pkgconf_list_t *dirlist, bool filter); +#endif +PKGCONF_API bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist); +PKGCONF_API void pkgconf_path_free(pkgconf_list_t *dirlist); +PKGCONF_API bool pkgconf_path_relocate(char *buf, size_t buflen); +PKGCONF_API void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src); +PKGCONF_API void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src); + +/* buffer.c */ +typedef struct pkgconf_buffer_ { + char *base; + char *end; +} pkgconf_buffer_t; + +PKGCONF_API void pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text); +PKGCONF_API void pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte); +PKGCONF_API void pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer); +PKGCONF_API void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer); +static inline const char *pkgconf_buffer_str(const pkgconf_buffer_t *buffer) { + return buffer->base; +} + +static inline size_t pkgconf_buffer_len(const pkgconf_buffer_t *buffer) { + return (size_t)(ptrdiff_t)(buffer->end - buffer->base); +} + +static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) { + if (buffer->base == buffer->end) + return '\0'; + + return *(buffer->end - 1); +} + +#define PKGCONF_BUFFER_INITIALIZER { NULL, NULL } + +static inline void pkgconf_buffer_reset(pkgconf_buffer_t *buffer) { + pkgconf_buffer_finalize(buffer); + buffer->base = buffer->end = NULL; +} + +/* fileio.c */ +PKGCONF_API bool pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libpkgconf/meson.build b/libpkgconf/meson.build new file mode 100644 index 00000000000..5c393b0ef73 --- /dev/null +++ b/libpkgconf/meson.build @@ -0,0 +1,12 @@ +configure_file(input : 'config.h.meson', + output : 'config.h', + configuration : cdata) + + +install_headers('libpkgconf.h', + 'stdinc.h', + 'iter.h', + 'bsdstubs.h', + 'libpkgconf-api.h', + subdir : 'pkgconf/libpkgconf') + diff --git a/libpkgconf/parser.c b/libpkgconf/parser.c new file mode 100644 index 00000000000..3a66b8af2a8 --- /dev/null +++ b/libpkgconf/parser.c @@ -0,0 +1,115 @@ +/* + * parser.c + * rfc822 message parser + * + * Copyright (c) 2018 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f) + * + * Parse a .pc file into a pkgconf_pkg_t object structure. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param char* filename: The filename of the package file (including full path). + * :param FILE* f: The file object to read from. + * :returns: A ``pkgconf_pkg_t`` object which contains the package data. + * :rtype: pkgconf_pkg_t * + */ +void +pkgconf_parser_parse(FILE *f, void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, const char *filename) +{ + pkgconf_buffer_t readbuf = PKGCONF_BUFFER_INITIALIZER; + size_t lineno = 0; + bool continue_reading = true; + + while (continue_reading) + { + char op, *p, *key, *value; + bool warned_key_whitespace = false, warned_value_whitespace = false; + + continue_reading = pkgconf_fgetline(&readbuf, f); + lineno++; + + p = readbuf.base; + if (p == NULL) + continue; + while (*p && isspace((unsigned char)*p)) + p++; + if (*p && p != readbuf.base) + { + warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: whitespace encountered while parsing key section\n", + filename, lineno); + warned_key_whitespace = true; + } + key = p; + while (*p && (isalpha((unsigned char)*p) || isdigit((unsigned char)*p) || *p == '_' || *p == '.')) + p++; + + if (!isalpha((unsigned char)*key) && + !isdigit((unsigned char)*p)) + { + pkgconf_buffer_reset(&readbuf); + continue; + } + + while (*p && isspace((unsigned char)*p)) + { + if (!warned_key_whitespace) + { + warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: whitespace encountered while parsing key section\n", + filename, lineno); + warned_key_whitespace = true; + } + + /* set to null to avoid trailing spaces in key */ + *p = '\0'; + p++; + } + + op = *p; + if (*p != '\0') + { + *p = '\0'; + p++; + } + + while (*p && isspace((unsigned char)*p)) + p++; + + value = p; + p = value + (strlen(value) - 1); + while (*p && isspace((unsigned char) *p) && p > value) + { + if (!warned_value_whitespace && op == '=') + { + warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: trailing whitespace encountered while parsing value section\n", + filename, lineno); + warned_value_whitespace = true; + } + + *p = '\0'; + p--; + } + if (ops[(unsigned char) op]) + ops[(unsigned char) op](data, lineno, key, value); + + pkgconf_buffer_reset(&readbuf); + } + + pkgconf_buffer_finalize(&readbuf); +} diff --git a/libpkgconf/path.c b/libpkgconf/path.c new file mode 100644 index 00000000000..dda77f5664a --- /dev/null +++ b/libpkgconf/path.c @@ -0,0 +1,459 @@ +/* + * path.c + * filesystem path management + * + * Copyright (c) 2016 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +#if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32) +# include +# define PKGCONF_CACHE_INODES +#endif + +#ifdef _WIN32 +# define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH" +#endif + +static bool +#ifdef PKGCONF_CACHE_INODES +path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st) +#else +path_list_contains_entry(const char *text, pkgconf_list_t *dirlist) +#endif +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) + { + pkgconf_path_t *pn = n->data; + +#ifdef PKGCONF_CACHE_INODES + if (pn->handle_device == (void *)(intptr_t)st->st_dev && pn->handle_path == (void *)(intptr_t)st->st_ino) + return true; +#endif + + if (!strcmp(text, pn->path)) + return true; + } + + return false; +} + +/* + * !doc + * + * libpkgconf `path` module + * ======================== + * + * The `path` module provides functions for manipulating lists of paths in a cross-platform manner. Notably, + * it is used by the `pkgconf client` to parse the ``PKG_CONFIG_PATH``, ``PKG_CONFIG_LIBDIR`` and related environment + * variables. + */ + +static pkgconf_path_t * +prepare_path_node(const char *text, pkgconf_list_t *dirlist, bool filter) +{ + pkgconf_path_t *node; + char path[PKGCONF_ITEM_SIZE]; + + pkgconf_strlcpy(path, text, sizeof path); + pkgconf_path_relocate(path, sizeof path); + +#ifdef PKGCONF_CACHE_INODES + struct stat st; + + if (filter) + { + if (lstat(path, &st) == -1) + return NULL; + if (S_ISLNK(st.st_mode)) + { + char pathbuf[PKGCONF_ITEM_SIZE * 4]; + char *linkdest = realpath(path, pathbuf); + + if (linkdest != NULL && stat(linkdest, &st) == -1) + return NULL; + } + if (path_list_contains_entry(path, dirlist, &st)) + return NULL; + } +#else + if (filter && path_list_contains_entry(path, dirlist)) + return NULL; +#endif + + node = calloc(1, sizeof(pkgconf_path_t)); + if (node == NULL) + return NULL; + + node->path = strdup(path); + +#ifdef PKGCONF_CACHE_INODES + if (filter) { + node->handle_path = (void *)(intptr_t) st.st_ino; + node->handle_device = (void *)(intptr_t) st.st_dev; + } +#endif + + return node; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_path_add(const char *text, pkgconf_list_t *dirlist) + * + * Adds a path node to a path list. If the path is already in the list, do nothing. + * + * :param char* text: The path text to add as a path node. + * :param pkgconf_list_t* dirlist: The path list to add the path node to. + * :param bool filter: Whether to perform duplicate filtering. + * :return: nothing + */ +void +pkgconf_path_add(const char *text, pkgconf_list_t *dirlist, bool filter) +{ + pkgconf_path_t *node = prepare_path_node(text, dirlist, filter); + if (node == NULL) + return; + + pkgconf_node_insert_tail(&node->lnode, node, dirlist); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist) + * + * Prepends a path node to a path list. If the path is already in the list, do nothing. + * + * :param char* text: The path text to add as a path node. + * :param pkgconf_list_t* dirlist: The path list to add the path node to. + * :param bool filter: Whether to perform duplicate filtering. + * :return: nothing + */ +void +pkgconf_path_prepend(const char *text, pkgconf_list_t *dirlist, bool filter) +{ + pkgconf_path_t *node = prepare_path_node(text, dirlist, filter); + if (node == NULL) + return; + + pkgconf_node_insert(&node->lnode, node, dirlist); +} + +/* + * !doc + * + * .. c:function:: size_t pkgconf_path_split(const char *text, pkgconf_list_t *dirlist) + * + * Splits a given text input and inserts paths into a path list. + * + * :param char* text: The path text to split and add as path nodes. + * :param pkgconf_list_t* dirlist: The path list to have the path nodes added to. + * :param bool filter: Whether to perform duplicate filtering. + * :return: number of path nodes added to the path list + * :rtype: size_t + */ +size_t +pkgconf_path_split(const char *text, pkgconf_list_t *dirlist, bool filter) +{ + size_t count = 0; + char *workbuf, *p, *iter; + + if (text == NULL) + return 0; + + iter = workbuf = strdup(text); + while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL) + { + pkgconf_path_add(p, dirlist, filter); + + count++, iter = NULL; + } + free(workbuf); + + return count; +} + +/* + * !doc + * + * .. c:function:: size_t pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist) + * + * Adds the paths specified in an environment variable to a path list. If the environment variable is not set, + * an optional default set of paths is added. + * + * :param char* envvarname: The environment variable to look up. + * :param char* fallback: The fallback paths to use if the environment variable is not set. + * :param pkgconf_list_t* dirlist: The path list to add the path nodes to. + * :param bool filter: Whether to perform duplicate filtering. + * :return: number of path nodes added to the path list + * :rtype: size_t + */ +size_t +pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter) +{ + const char *data; + + data = getenv(envvarname); + if (data != NULL) + return pkgconf_path_split(data, dirlist, filter); + + if (fallback != NULL) + return pkgconf_path_split(fallback, dirlist, filter); + + /* no fallback and no environment variable, thusly no nodes added */ + return 0; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) + * + * Checks whether a path has a matching prefix in a path list. + * + * :param char* path: The path to check against a path list. + * :param pkgconf_list_t* dirlist: The path list to check the path against. + * :return: true if the path list has a matching prefix, otherwise false + * :rtype: bool + */ +bool +pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist) +{ + pkgconf_node_t *n = NULL; + char relocated[PKGCONF_ITEM_SIZE]; + const char *cpath = path; + + pkgconf_strlcpy(relocated, path, sizeof relocated); + if (pkgconf_path_relocate(relocated, sizeof relocated)) + cpath = relocated; + + PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n) + { + pkgconf_path_t *pnode = n->data; + + if (!strcmp(pnode->path, cpath)) + return true; + } + + return false; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) + * + * Copies a path list to another path list. + * + * :param pkgconf_list_t* dst: The path list to copy to. + * :param pkgconf_list_t* src: The path list to copy from. + * :return: nothing + */ +void +pkgconf_path_copy_list(pkgconf_list_t *dst, const pkgconf_list_t *src) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(src->head, n) + { + pkgconf_path_t *srcpath = n->data, *path; + + path = calloc(1, sizeof(pkgconf_path_t)); + if (path == NULL) + continue; + + path->path = strdup(srcpath->path); + +#ifdef PKGCONF_CACHE_INODES + path->handle_path = srcpath->handle_path; + path->handle_device = srcpath->handle_device; +#endif + + pkgconf_node_insert_tail(&path->lnode, path, dst); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) + * + * Copies a path list to another path list. + * + * :param pkgconf_list_t* dst: The path list to copy to. + * :param pkgconf_list_t* src: The path list to copy from. + * :return: nothing + */ +void +pkgconf_path_prepend_list(pkgconf_list_t *dst, const pkgconf_list_t *src) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(src->head, n) + { + pkgconf_path_t *srcpath = n->data, *path; + + path = calloc(1, sizeof(pkgconf_path_t)); + if (path == NULL) + continue; + + path->path = strdup(srcpath->path); + +#ifdef PKGCONF_CACHE_INODES + path->handle_path = srcpath->handle_path; + path->handle_device = srcpath->handle_device; +#endif + + pkgconf_node_insert(&path->lnode, path, dst); + } +} + +/* + * !doc + * + * .. c:function:: void pkgconf_path_free(pkgconf_list_t *dirlist) + * + * Releases any path nodes attached to the given path list. + * + * :param pkgconf_list_t* dirlist: The path list to clean up. + * :return: nothing + */ +void +pkgconf_path_free(pkgconf_list_t *dirlist) +{ + pkgconf_node_t *n, *tn; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(dirlist->head, tn, n) + { + pkgconf_path_t *pnode = n->data; + + free(pnode->path); + free(pnode); + } + + pkgconf_list_zero(dirlist); +} + +static char * +normpath(const char *path) +{ + if (!path) + return NULL; + + char *copy = strdup(path); + if (NULL == copy) + return NULL; + char *ptr = copy; + + for (int ii = 0; copy[ii]; ii++) + { + *ptr++ = path[ii]; + if ('/' == path[ii]) + { + ii++; + while ('/' == path[ii]) + ii++; + ii--; + } + } + *ptr = '\0'; + + return copy; +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen) + * + * Relocates a path, possibly calling normpath() on it. + * + * :param char* buf: The path to relocate. + * :param size_t buflen: The buffer length the path is contained in. + * :return: true on success, false on error + * :rtype: bool + */ +bool +pkgconf_path_relocate(char *buf, size_t buflen) +{ + char *tmpbuf; + + if ((tmpbuf = normpath(buf)) != NULL) + { + size_t tmpbuflen = strlen(tmpbuf); + if (tmpbuflen > buflen) + { + free(tmpbuf); + return false; + } + + pkgconf_strlcpy(buf, tmpbuf, buflen); + free(tmpbuf); + } + + return true; +} + +#ifdef _WIN32 +/* + * !doc + * + * .. c:function:: void pkgconf_path_build_from_registry(HKEY hKey, pkgconf_list_t *dir_list, bool filter) + * + * Adds paths to a directory list discovered from a given registry key. + * + * :param HKEY hKey: The registry key to enumerate. + * :param pkgconf_list_t* dir_list: The directory list to append enumerated paths to. + * :param bool filter: Whether duplicate paths should be filtered. + * :return: number of path nodes added to the list + * :rtype: size_t + */ +size_t +pkgconf_path_build_from_registry(void *hKey, pkgconf_list_t *dir_list, bool filter) +{ + HKEY key; + int i = 0; + size_t added = 0; + + char buf[16384]; /* per registry limits */ + DWORD bufsize = sizeof buf; + if (RegOpenKeyEx(hKey, PKG_CONFIG_REG_KEY, + 0, KEY_READ, &key) != ERROR_SUCCESS) + return 0; + + while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL) + == ERROR_SUCCESS) + { + char pathbuf[PKGCONF_ITEM_SIZE]; + DWORD type; + DWORD pathbuflen = sizeof pathbuf; + + if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen) + == ERROR_SUCCESS && type == REG_SZ) + { + pkgconf_path_add(pathbuf, dir_list, filter); + added++; + } + + bufsize = sizeof buf; + } + + RegCloseKey(key); + return added; +} +#endif diff --git a/libpkgconf/personality.c b/libpkgconf/personality.c new file mode 100644 index 00000000000..6c017e91277 --- /dev/null +++ b/libpkgconf/personality.c @@ -0,0 +1,359 @@ +/* + * personality.c + * libpkgconf cross-compile personality database + * + * Copyright (c) 2018 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +/* + * !doc + * + * libpkgconf `personality` module + * ========================= + */ + +#ifdef _WIN32 +# define strcasecmp _stricmp +#endif + +/* + * Increment each time the default personality is inited, decrement each time + * it's deinited. Whenever it is 0, then the deinit frees the personality. In + * that case an additional call to init will create it anew. + */ +static unsigned default_personality_init = 0; + +static pkgconf_cross_personality_t default_personality = { + .name = "default", +}; + +static inline void +build_default_search_path(pkgconf_list_t* dirlist) +{ +#ifdef _WIN32 + char namebuf[MAX_PATH]; + char outbuf[MAX_PATH]; + char *p; + + int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf); + char * winslash; + namebuf[sizepath] = '\0'; + while ((winslash = strchr (namebuf, '\\')) != NULL) + { + *winslash = '/'; + } + p = strrchr(namebuf, '/'); + if (p == NULL) + pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true); + + *p = '\0'; + pkgconf_strlcpy(outbuf, namebuf, sizeof outbuf); + pkgconf_strlcat(outbuf, "/", sizeof outbuf); + pkgconf_strlcat(outbuf, "../lib/pkgconfig", sizeof outbuf); + pkgconf_path_add(outbuf, dirlist, true); + pkgconf_strlcpy(outbuf, namebuf, sizeof outbuf); + pkgconf_strlcat(outbuf, "/", sizeof outbuf); + pkgconf_strlcat(outbuf, "../share/pkgconfig", sizeof outbuf); + pkgconf_path_add(outbuf, dirlist, true); +#elif __HAIKU__ + char **paths; + size_t count; + if (find_paths(B_FIND_PATH_DEVELOP_LIB_DIRECTORY, "pkgconfig", &paths, &count) == B_OK) { + for (size_t i = 0; i < count; i++) + pkgconf_path_add(paths[i], dirlist, true); + free(paths); + paths = NULL; + } + if (find_paths(B_FIND_PATH_DATA_DIRECTORY, "pkgconfig", &paths, &count) == B_OK) { + for (size_t i = 0; i < count; i++) + pkgconf_path_add(paths[i], dirlist, true); + free(paths); + paths = NULL; + } +#else + pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true); +#endif +} + +/* + * !doc + * + * .. c:function:: const pkgconf_cross_personality_t *pkgconf_cross_personality_default(void) + * + * Returns the default cross-compile personality. + * + * Not thread safe. + * + * :rtype: pkgconf_cross_personality_t* + * :return: the default cross-compile personality + */ +pkgconf_cross_personality_t * +pkgconf_cross_personality_default(void) +{ + if (default_personality_init) { + ++default_personality_init; + return &default_personality; + } + + build_default_search_path(&default_personality.dir_list); + + pkgconf_path_split(SYSTEM_LIBDIR, &default_personality.filter_libdirs, false); + pkgconf_path_split(SYSTEM_INCLUDEDIR, &default_personality.filter_includedirs, false); + + ++default_personality_init; + return &default_personality; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *) + * + * Destroys a cross personality object and/or decreases the reference count on the + * default cross personality object. + * + * Not thread safe. + * + * :rtype: void + */ +void +pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *personality) +{ + /* allow NULL parameter for API backwards compatibility */ + if (personality == NULL) + return; + + /* XXX: this hack is rather ugly, but it works for now... */ + if (personality == &default_personality && --default_personality_init > 0) + return; + + pkgconf_path_free(&personality->dir_list); + pkgconf_path_free(&personality->filter_libdirs); + pkgconf_path_free(&personality->filter_includedirs); + + if (personality->sysroot_dir != NULL) + free(personality->sysroot_dir); + + if (personality == &default_personality) + return; + + if (personality->name != NULL) + free(personality->name); + + free(personality); +} + +#ifndef PKGCONF_LITE +static bool +valid_triplet(const char *triplet) +{ + const char *c = triplet; + + for (; *c; c++) + if (!isalnum((unsigned char)*c) && *c != '-' && *c != '_') + return false; + + return true; +} + +typedef void (*personality_keyword_func_t)(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value); +typedef struct { + const char *keyword; + const personality_keyword_func_t func; + const ptrdiff_t offset; +} personality_keyword_pair_t; + +static void +personality_bool_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +{ + (void) keyword; + (void) lineno; + + bool *dest = (bool *)((char *) p + offset); + *dest = strcasecmp(value, "true") || strcasecmp(value, "yes") || *value == '1'; +} + +static void +personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +{ + (void) keyword; + (void) lineno; + + char **dest = (char **)((char *) p + offset); + *dest = strdup(value); +} + +static void +personality_fragment_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value) +{ + (void) keyword; + (void) lineno; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) p + offset); + pkgconf_path_split(value, dest, false); +} + +/* keep in alphabetical order! */ +static const personality_keyword_pair_t personality_keyword_pairs[] = { + {"DefaultSearchPaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, dir_list)}, + {"SysrootDir", personality_copy_func, offsetof(pkgconf_cross_personality_t, sysroot_dir)}, + {"SystemIncludePaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, filter_includedirs)}, + {"SystemLibraryPaths", personality_fragment_func, offsetof(pkgconf_cross_personality_t, filter_libdirs)}, + {"Triplet", personality_copy_func, offsetof(pkgconf_cross_personality_t, name)}, + {"WantDefaultPure", personality_bool_func, offsetof(pkgconf_cross_personality_t, want_default_pure)}, + {"WantDefaultStatic", personality_bool_func, offsetof(pkgconf_cross_personality_t, want_default_static)}, +}; + +static int +personality_keyword_pair_cmp(const void *key, const void *ptr) +{ + const personality_keyword_pair_t *pair = ptr; + return strcasecmp(key, pair->keyword); +} + +static void +personality_keyword_set(pkgconf_cross_personality_t *p, const size_t lineno, const char *keyword, char *value) +{ + const personality_keyword_pair_t *pair = bsearch(keyword, + personality_keyword_pairs, PKGCONF_ARRAY_SIZE(personality_keyword_pairs), + sizeof(personality_keyword_pair_t), personality_keyword_pair_cmp); + + if (pair == NULL || pair->func == NULL) + return; + + pair->func(p, keyword, lineno, pair->offset, value); +} + +static const pkgconf_parser_operand_func_t personality_parser_ops[256] = { + [':'] = (pkgconf_parser_operand_func_t) personality_keyword_set +}; + +static void personality_warn_func(void *p, const char *fmt, ...) PRINTFLIKE(2, 3); + +static void +personality_warn_func(void *p, const char *fmt, ...) +{ + va_list va; + + (void) p; + + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); +} + +static pkgconf_cross_personality_t * +load_personality_with_path(const char *path, const char *triplet, bool datadir) +{ + char pathbuf[PKGCONF_ITEM_SIZE]; + FILE *f; + pkgconf_cross_personality_t *p; + + /* if triplet is null, assume that path is a direct path to the personality file */ + if (triplet == NULL) + pkgconf_strlcpy(pathbuf, path, sizeof pathbuf); + else if (datadir) + snprintf(pathbuf, sizeof pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet); + else + snprintf(pathbuf, sizeof pathbuf, "%s/%s.personality", path, triplet); + + p = calloc(1, sizeof(pkgconf_cross_personality_t)); + if (p == NULL) + return NULL; + + if (triplet != NULL) + p->name = strdup(triplet); + + f = fopen(pathbuf, "r"); + if (f == NULL) { + pkgconf_cross_personality_deinit(p); + return NULL; + } + + pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pathbuf); + + return p; +} + +/* + * !doc + * + * .. c:function:: pkgconf_cross_personality_t *pkgconf_cross_personality_find(const char *triplet) + * + * Attempts to find a cross-compile personality given a triplet. + * + * :rtype: pkgconf_cross_personality_t* + * :return: the default cross-compile personality + */ +pkgconf_cross_personality_t * +pkgconf_cross_personality_find(const char *triplet) +{ + pkgconf_list_t plist = PKGCONF_LIST_INITIALIZER; + pkgconf_node_t *n; + pkgconf_cross_personality_t *out = NULL; +#if ! defined(_WIN32) && ! defined(__HAIKU__) + char pathbuf[PKGCONF_ITEM_SIZE]; + const char *envvar; +#endif + + out = load_personality_with_path(triplet, NULL, false); + if (out != NULL) + return out; + + if (!valid_triplet(triplet)) + return NULL; + +#if ! defined(_WIN32) && ! defined(__HAIKU__) + envvar = getenv("XDG_DATA_HOME"); + if (envvar != NULL) + pkgconf_path_add(envvar, &plist, true); + else { + envvar = getenv("HOME"); + if (envvar != NULL) { + pkgconf_strlcpy(pathbuf, envvar, sizeof pathbuf); + pkgconf_strlcat(pathbuf, "/.local/share", sizeof pathbuf); + pkgconf_path_add(pathbuf, &plist, true); + } + } + + pkgconf_path_build_from_environ("XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true); + + PKGCONF_FOREACH_LIST_ENTRY(plist.head, n) + { + pkgconf_path_t *pn = n->data; + + out = load_personality_with_path(pn->path, triplet, true); + if (out != NULL) + goto finish; + } + pkgconf_path_free(&plist); +#endif + + pkgconf_path_split(PERSONALITY_PATH, &plist, true); + + PKGCONF_FOREACH_LIST_ENTRY(plist.head, n) + { + pkgconf_path_t *pn = n->data; + + out = load_personality_with_path(pn->path, triplet, false); + if (out != NULL) + goto finish; + } + +finish: + pkgconf_path_free(&plist); + return out != NULL ? out : pkgconf_cross_personality_default(); +} +#endif diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c new file mode 100644 index 00000000000..491c0defcd6 --- /dev/null +++ b/libpkgconf/pkg.c @@ -0,0 +1,1935 @@ +/* + * pkg.c + * higher-level dependency graph compilation, management and manipulation + * + * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include + +#ifndef _WIN32 +#include // open +#include // basename/dirname +#include // lstat, S_ISLNK +#include // close, readlinkat + +#include +#endif + +/* + * !doc + * + * libpkgconf `pkg` module + * ======================= + * + * The `pkg` module provides dependency resolution services and the overall `.pc` file parsing + * routines. + */ + +#ifdef _WIN32 +# undef PKG_DEFAULT_PATH +# define PKG_DEFAULT_PATH "../lib/pkgconfig;../share/pkgconfig" +# define strncasecmp _strnicmp +# define strcasecmp _stricmp +#endif + +#define PKG_CONFIG_EXT ".pc" + +static unsigned int +pkgconf_pkg_traverse_main(pkgconf_client_t *client, + pkgconf_pkg_t *root, + pkgconf_pkg_traverse_func_t func, + void *data, + int maxdepth, + unsigned int skip_flags); + +static inline bool +str_has_suffix(const char *str, const char *suffix) +{ + size_t str_len = strlen(str); + size_t suf_len = strlen(suffix); + + if (str_len < suf_len) + return false; + + return !strncasecmp(str + str_len - suf_len, suffix, suf_len); +} + +static char * +pkg_get_parent_dir(pkgconf_pkg_t *pkg) +{ + char buf[PKGCONF_ITEM_SIZE], *pathbuf; + + pkgconf_strlcpy(buf, pkg->filename, sizeof buf); +#ifndef _WIN32 + /* + * We want to resolve symlinks, since ${pcfiledir} should point to the + * parent of the file symlinked to. + */ + struct stat path_stat; + while (!lstat(buf, &path_stat) && S_ISLNK(path_stat.st_mode)) + { + /* + * Have to split the path into the dir + file components, + * in order to extract the directory file descriptor. + * + * The nomenclature here uses the + * + * ln + * + * model. + */ + char basenamebuf[PKGCONF_ITEM_SIZE]; + pkgconf_strlcpy(basenamebuf, buf, sizeof(basenamebuf)); + const char* targetfilename = basename(basenamebuf); + + char dirnamebuf[PKGCONF_ITEM_SIZE]; + pkgconf_strlcpy(dirnamebuf, buf, sizeof(dirnamebuf)); + const char* targetdir = dirname(dirnamebuf); + + const int dirfd = open(targetdir, O_DIRECTORY); + if (dirfd == -1) + break; + + char sourcebuf[PKGCONF_ITEM_SIZE]; + ssize_t len = readlinkat(dirfd, targetfilename, sourcebuf, sizeof(sourcebuf) - 1); + close(dirfd); + + if (len == -1) + break; + sourcebuf[len] = '\0'; + + memset(buf, '\0', sizeof buf); + /* + * The logic here can be a bit tricky, so here's a table: + * + * | | result + * ----------------------------------------------------------------------- + * /bar (absolute) | foo/link (relative) | /bar (absolute) + * ../bar (relative) | foo/link (relative) | foo/../bar (relative) + * /bar (absolute) | /foo/link (absolute) | /bar (absolute) + * ../bar (relative) | /foo/link (absolute) | /foo/../bar (relative) + */ + if ((sourcebuf[0] != '/') /* absolute path in wins */ + && (strcmp(targetdir, "."))) /* do not prepend "." */ + { + pkgconf_strlcat(buf, targetdir, sizeof buf); + pkgconf_strlcat(buf, "/", sizeof buf); + } + pkgconf_strlcat(buf, sourcebuf, sizeof buf); + } +#endif + + pathbuf = strrchr(buf, PKG_DIR_SEP_S); + if (pathbuf == NULL) + pathbuf = strrchr(buf, '/'); + if (pathbuf != NULL) + pathbuf[0] = '\0'; + + return strdup(buf); +} + +typedef void (*pkgconf_pkg_parser_keyword_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value); +typedef struct { + const char *keyword; + const pkgconf_pkg_parser_keyword_func_t func; + const ptrdiff_t offset; +} pkgconf_pkg_parser_keyword_pair_t; + +static int pkgconf_pkg_parser_keyword_pair_cmp(const void *key, const void *ptr) +{ + const pkgconf_pkg_parser_keyword_pair_t *pair = ptr; + return strcasecmp(key, pair->keyword); +} + +static void +pkgconf_pkg_parser_tuple_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) lineno; + + char **dest = (char **)((char *) pkg + offset); + *dest = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); +} + +static void +pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) lineno; + char *p, *i; + size_t len; + char **dest = (char **)((char *) pkg + offset); + + /* cut at any detected whitespace */ + p = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags); + + len = strcspn(p, " \t"); + if (len != strlen(p)) + { + i = p + (ptrdiff_t) len; + *i = '\0'; + + pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: malformed version field with whitespace, trimming to [%s]\n", pkg->filename, + lineno, p); + } + + *dest = p; +} + +static void +pkgconf_pkg_parser_fragment_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + + /* we patch client-wide sysroot dir and then patch it back when it is overridden */ + char *sysroot_dir = client->sysroot_dir; + char *pkg_sysroot_dir = pkgconf_tuple_find(client, &pkg->vars, "pc_sysrootdir"); + if (pkg_sysroot_dir != NULL) + client->sysroot_dir = pkg_sysroot_dir; + + bool ret = pkgconf_fragment_parse(client, dest, &pkg->vars, value, pkg->flags); + client->sysroot_dir = sysroot_dir; + + if (!ret) + { + pkgconf_warn(client, "%s:" SIZE_FMT_SPECIFIER ": warning: unable to parse field '%s' into an argument vector, value [%s]\n", pkg->filename, + lineno, keyword, value); + } +} + +static void +pkgconf_pkg_parser_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) lineno; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + pkgconf_dependency_parse(client, pkg, dest, value, 0); +} + +/* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as an "internal" dependency. */ +static void +pkgconf_pkg_parser_internal_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) lineno; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_INTERNAL); +} + +/* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as a "private" dependency. */ +static void +pkgconf_pkg_parser_private_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const size_t lineno, const ptrdiff_t offset, const char *value) +{ + (void) keyword; + (void) lineno; + + pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset); + pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_PRIVATE); +} + +/* keep this in alphabetical order */ +static const pkgconf_pkg_parser_keyword_pair_t pkgconf_pkg_parser_keyword_funcs[] = { + {"CFLAGS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags)}, + {"CFLAGS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags_private)}, + {"Conflicts", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, conflicts)}, + {"Copyright", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, copyright)}, + {"Description", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, description)}, + {"LIBS", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs)}, + {"LIBS.private", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs_private)}, + {"License", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, license)}, + {"Maintainer", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, maintainer)}, + {"Name", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, realname)}, + {"Provides", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, provides)}, + {"Requires", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, required)}, + {"Requires.internal", pkgconf_pkg_parser_internal_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, + {"Requires.private", pkgconf_pkg_parser_private_dependency_func, offsetof(pkgconf_pkg_t, requires_private)}, + {"URL", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, url)}, + {"Version", pkgconf_pkg_parser_version_func, offsetof(pkgconf_pkg_t, version)}, +}; + +static void +pkgconf_pkg_parser_keyword_set(void *opaque, const size_t lineno, const char *keyword, const char *value) +{ + pkgconf_pkg_t *pkg = opaque; + + const pkgconf_pkg_parser_keyword_pair_t *pair = bsearch(keyword, + pkgconf_pkg_parser_keyword_funcs, PKGCONF_ARRAY_SIZE(pkgconf_pkg_parser_keyword_funcs), + sizeof(pkgconf_pkg_parser_keyword_pair_t), pkgconf_pkg_parser_keyword_pair_cmp); + + if (pair == NULL || pair->func == NULL) + return; + + pair->func(pkg->owner, pkg, keyword, lineno, pair->offset, value); +} + +static const char * +determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen) +{ + char *pathiter; + + pkgconf_strlcpy(buf, pkg->filename, buflen); + pkgconf_path_relocate(buf, buflen); + + pathiter = strrchr(buf, PKG_DIR_SEP_S); + if (pathiter == NULL) + pathiter = strrchr(buf, '/'); + if (pathiter != NULL) + pathiter[0] = '\0'; + + pathiter = strrchr(buf, PKG_DIR_SEP_S); + if (pathiter == NULL) + pathiter = strrchr(buf, '/'); + if (pathiter == NULL) + return NULL; + + /* parent dir is not pkgconfig, can't relocate then */ + if (strcmp(pathiter + 1, "pkgconfig")) + return NULL; + + /* okay, work backwards and do it again. */ + pathiter[0] = '\0'; + pathiter = strrchr(buf, PKG_DIR_SEP_S); + if (pathiter == NULL) + pathiter = strrchr(buf, '/'); + if (pathiter == NULL) + return NULL; + + pathiter[0] = '\0'; + + return buf; +} + +/* + * Takes a real path and converts it to a pkgconf value. This means normalizing + * directory separators and escaping things (only spaces covered atm). + * + * This is useful for things like prefix/pcfiledir which might get injected + * at runtime and are not sourced from the .pc file. + * + * "C:\foo bar\baz" -> "C:/foo\ bar/baz" + * "/foo bar/baz" -> "/foo\ bar/baz" + */ +static char * +convert_path_to_value(const char *path) +{ + char *buf = calloc(1, (strlen(path) + 1) * 2); + if (buf == NULL) + return NULL; + + char *bptr = buf; + const char *i; + + for (i = path; *i != '\0'; i++) + { + if (*i == PKG_DIR_SEP_S) + *bptr++ = '/'; + else if (*i == ' ') { + *bptr++ = '\\'; + *bptr++ = *i; + } else + *bptr++ = *i; + } + + return buf; +} + +static void +remove_additional_separators(char *buf) +{ + char *p = buf; + + while (*p) { + if (*p == '/') { + char *q; + + q = ++p; + while (*q && *q == '/') + q++; + + if (p != q) + memmove (p, q, strlen (q) + 1); + } else { + p++; + } + } +} + +static void +canonicalize_path(char *buf) +{ + remove_additional_separators(buf); +} + +static bool +is_path_prefix_equal(const char *path1, const char *path2, size_t path2_len) +{ +#ifdef _WIN32 + return !_strnicmp(path1, path2, path2_len); +#else + return !strncmp(path1, path2, path2_len); +#endif +} + +static void +pkgconf_pkg_parser_value_set(void *opaque, const size_t lineno, const char *keyword, const char *value) +{ + char canonicalized_value[PKGCONF_ITEM_SIZE]; + pkgconf_pkg_t *pkg = opaque; + + (void) lineno; + + pkgconf_strlcpy(canonicalized_value, value, sizeof canonicalized_value); + canonicalize_path(canonicalized_value); + + /* Some pc files will use absolute paths for all of their directories + * which is broken when redefining the prefix. We try to outsmart the + * file and rewrite any directory that starts with the same prefix. + */ + if (pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX && pkg->orig_prefix + && is_path_prefix_equal(canonicalized_value, pkg->orig_prefix->value, strlen(pkg->orig_prefix->value))) + { + char newvalue[PKGCONF_ITEM_SIZE]; + + pkgconf_strlcpy(newvalue, pkg->prefix->value, sizeof newvalue); + pkgconf_strlcat(newvalue, canonicalized_value + strlen(pkg->orig_prefix->value), sizeof newvalue); + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, newvalue, false, pkg->flags); + } + else if (strcmp(keyword, pkg->owner->prefix_varname) || !(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX)) + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); + else + { + char pathbuf[PKGCONF_ITEM_SIZE]; + const char *relvalue = determine_prefix(pkg, pathbuf, sizeof pathbuf); + + if (relvalue != NULL) + { + char *prefix_value = convert_path_to_value(relvalue); + pkg->orig_prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, "orig_prefix", canonicalized_value, true, pkg->flags); + pkg->prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, prefix_value, false, pkg->flags); + free(prefix_value); + } + else + pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags); + } +} + +typedef struct { + const char *field; + const ptrdiff_t offset; +} pkgconf_pkg_validity_check_t; + +static const pkgconf_pkg_validity_check_t pkgconf_pkg_validations[] = { + {"Name", offsetof(pkgconf_pkg_t, realname)}, + {"Description", offsetof(pkgconf_pkg_t, description)}, + {"Version", offsetof(pkgconf_pkg_t, version)}, +}; + +static const pkgconf_parser_operand_func_t pkg_parser_funcs[256] = { + [':'] = pkgconf_pkg_parser_keyword_set, + ['='] = pkgconf_pkg_parser_value_set +}; + +static void pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) PRINTFLIKE(2, 3); + +static void +pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...) +{ + char buf[PKGCONF_ITEM_SIZE]; + va_list va; + + va_start(va, fmt); + vsnprintf(buf, sizeof buf, fmt, va); + va_end(va); + + pkgconf_warn(pkg->owner, "%s", buf); +} + +static bool +pkgconf_pkg_validate(const pkgconf_client_t *client, const pkgconf_pkg_t *pkg) +{ + size_t i; + bool valid = true; + + for (i = 0; i < PKGCONF_ARRAY_SIZE(pkgconf_pkg_validations); i++) + { + char **p = (char **)((char *) pkg + pkgconf_pkg_validations[i].offset); + + if (*p != NULL) + continue; + + pkgconf_warn(client, "%s: warning: file does not declare a `%s' field\n", pkg->filename, pkgconf_pkg_validations[i].field); + valid = false; + } + + return valid; +} + +static void +pkg_free_object(pkgconf_pkg_t *pkg) +{ + if (pkg->flags & PKGCONF_PKG_PROPF_PRELOADED) + pkgconf_node_delete(&pkg->preload_node, &pkg->owner->preloaded_pkgs); + + if (pkg->id != NULL) + free(pkg->id); + + if (pkg->filename != NULL) + free(pkg->filename); + + if (pkg->realname != NULL) + free(pkg->realname); + + if (pkg->version != NULL) + free(pkg->version); + + if (pkg->description != NULL) + free(pkg->description); + + if (pkg->url != NULL) + free(pkg->url); + + if (pkg->pc_filedir != NULL) + free(pkg->pc_filedir); + + if (pkg->license != NULL) + free(pkg->license); + + if (pkg->maintainer != NULL) + free(pkg->maintainer); + + if (pkg->copyright != NULL) + free(pkg->copyright); + + if (pkg->why != NULL) + free(pkg->why); + + free(pkg); +} + +static void +pkg_free_lists(pkgconf_pkg_t *pkg) +{ + pkgconf_dependency_free(&pkg->required); + pkgconf_dependency_free(&pkg->requires_private); + pkgconf_dependency_free(&pkg->conflicts); + pkgconf_dependency_free(&pkg->provides); + + pkgconf_fragment_free(&pkg->cflags); + pkgconf_fragment_free(&pkg->cflags_private); + pkgconf_fragment_free(&pkg->libs); + pkgconf_fragment_free(&pkg->libs_private); + + pkgconf_tuple_free(&pkg->vars); +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_path(const pkgconf_client_t *client, const char *filename, unsigned int flags) + * + * Parse a .pc file into a pkgconf_pkg_t object structure. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param char* filename: The filename of the package file (including full path). + * :param FILE* f: The file object to read from. + * :param uint flags: The flags to use when parsing. + * :returns: A ``pkgconf_pkg_t`` object which contains the package data. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_pkg_new_from_path(pkgconf_client_t *client, const char *filename, unsigned int flags) +{ + pkgconf_pkg_t *pkg; + char *idptr; + FILE *f; + + /* make sure we only load .pc files */ + if (!str_has_suffix(filename, PKG_CONFIG_EXT)) + return NULL; + + f = fopen(filename, "r"); + if (f == NULL) + return NULL; + + pkg = calloc(1, sizeof(pkgconf_pkg_t)); + if (pkg == NULL) + { + fclose(f); + return NULL; + } + + pkg->owner = client; + pkg->flags = flags; + + pkg->filename = strdup(filename); + if (pkg->filename == NULL) + { + fclose(f); + pkg_free_object(pkg); + return NULL; + } + + pkg->pc_filedir = pkg_get_parent_dir(pkg); + if (pkg->pc_filedir == NULL) + { + fclose(f); + pkg_free_object(pkg); + return NULL; + } + + char *pc_filedir_value = convert_path_to_value(pkg->pc_filedir); + pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pc_filedir_value, true, pkg->flags); + free(pc_filedir_value); + + /* If pc_filedir is outside of sysroot_dir, override sysroot_dir for this + * package. + * See https://github.com/pkgconf/pkgconf/issues/213 + */ + if (client->sysroot_dir && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir))) + pkgconf_tuple_add(client, &pkg->vars, "pc_sysrootdir", "", false, pkg->flags); + + /* make module id */ + if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL) + idptr++; + else + idptr = pkg->filename; + +#ifdef _WIN32 + /* On Windows, both \ and / are allowed in paths, so we have to chop both. + * strrchr() took us to the last \ in that case, so we just have to see if + * it is followed by a /. If so, lop it off. + */ + char *mungeptr; + if ((mungeptr = strrchr(idptr, '/')) != NULL) + idptr = ++mungeptr; +#endif + + pkg->id = strdup(idptr); + if (pkg->id == NULL) + { + fclose(f); + pkg_free_lists(pkg); + pkg_free_object(pkg); + return NULL; + } + + idptr = strrchr(pkg->id, '.'); + if (idptr) + *idptr = '\0'; + + if (pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED) + { + idptr = strrchr(pkg->id, '-'); + if (idptr) + *idptr = '\0'; + } + + pkgconf_parser_parse(f, pkg, pkg_parser_funcs, (pkgconf_parser_warn_func_t) pkg_warn_func, pkg->filename); + fclose(f); + + if (!pkgconf_pkg_validate(client, pkg)) + { + pkgconf_warn(client, "%s: warning: skipping invalid file\n", pkg->filename); + pkgconf_pkg_free(client, pkg); + return NULL; + } + + pkgconf_dependency_t *dep = pkgconf_dependency_add(client, &pkg->provides, pkg->id, pkg->version, PKGCONF_CMP_EQUAL, 0); + pkgconf_dependency_unref(dep->owner, dep); + + return pkgconf_pkg_ref(client, pkg); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Releases all releases for a given ``pkgconf_pkg_t`` object. + * + * :param pkgconf_client_t* client: The client which owns the ``pkgconf_pkg_t`` object, `pkg`. + * :param pkgconf_pkg_t* pkg: The package to free. + * :return: nothing + */ +void +pkgconf_pkg_free(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + if (pkg == NULL) + return; + + if (pkg->flags & PKGCONF_PKG_PROPF_STATIC && !(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)) + return; + + pkgconf_cache_remove(client, pkg); + + pkg_free_lists(pkg); + + if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL) + return; + + pkg_free_object(pkg); +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_ref(const pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Adds an additional reference to the package object. + * + * :param pkgconf_client_t* client: The pkgconf client object which owns the package being referenced. + * :param pkgconf_pkg_t* pkg: The package object being referenced. + * :return: The package itself with an incremented reference count. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_pkg_ref(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + if (pkg->owner != NULL && pkg->owner != client) + PKGCONF_TRACE(client, "WTF: client %p refers to package %p owned by other client %p", client, pkg, pkg->owner); + + pkg->refcount++; + PKGCONF_TRACE(client, "%s refcount@%p: %d", pkg->id, pkg, pkg->refcount); + + return pkg; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg) + * + * Releases a reference on the package object. If the reference count is 0, then also free the package. + * + * :param pkgconf_client_t* client: The pkgconf client object which owns the package being dereferenced. + * :param pkgconf_pkg_t* pkg: The package object being dereferenced. + * :return: nothing + */ +void +pkgconf_pkg_unref(pkgconf_client_t *client, pkgconf_pkg_t *pkg) +{ + if (pkg == NULL) { + PKGCONF_TRACE(client, "WTF: client %p unrefs a NULL package", client); + return; + } + + if (pkg->owner != NULL && pkg->owner != client) + PKGCONF_TRACE(client, "WTF: client %p unrefs package %p owned by other client %p", client, pkg, pkg->owner); + + pkg->refcount--; + PKGCONF_TRACE(pkg->owner, "%s refcount@%p: %d", pkg->id, pkg, pkg->refcount); + + if (pkg->refcount <= 0) + pkgconf_pkg_free(pkg->owner, pkg); +} + +static inline pkgconf_pkg_t * +pkgconf_pkg_try_specific_path(pkgconf_client_t *client, const char *path, const char *name) +{ + pkgconf_pkg_t *pkg = NULL; + char locbuf[PKGCONF_ITEM_SIZE]; + char uninst_locbuf[PKGCONF_ITEM_SIZE]; + + PKGCONF_TRACE(client, "trying path: %s for %s", path, name); + + snprintf(locbuf, sizeof locbuf, "%s%c%s" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, name); + snprintf(uninst_locbuf, sizeof uninst_locbuf, "%s%c%s-uninstalled" PKG_CONFIG_EXT, path, PKG_DIR_SEP_S, name); + + if (!(client->flags & PKGCONF_PKG_PKGF_NO_UNINSTALLED)) + pkg = pkgconf_pkg_new_from_path(client, uninst_locbuf, PKGCONF_PKG_PROPF_UNINSTALLED); + + if (pkg == NULL) + pkg = pkgconf_pkg_new_from_path(client, locbuf, 0); + + if (pkg != NULL) + PKGCONF_TRACE(client, "found%s: %s", pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED ? " (uninstalled)" : "", uninst_locbuf); + + return pkg; +} + +static pkgconf_pkg_t * +pkgconf_pkg_scan_dir(pkgconf_client_t *client, const char *path, void *data, pkgconf_pkg_iteration_func_t func) +{ + DIR *dir; + struct dirent *dirent; + pkgconf_pkg_t *outpkg = NULL; + + dir = opendir(path); + if (dir == NULL) + return NULL; + + PKGCONF_TRACE(client, "scanning dir [%s]", path); + + for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) + { + char filebuf[PKGCONF_ITEM_SIZE]; + pkgconf_pkg_t *pkg; + + pkgconf_strlcpy(filebuf, path, sizeof filebuf); + pkgconf_strlcat(filebuf, "/", sizeof filebuf); + pkgconf_strlcat(filebuf, dirent->d_name, sizeof filebuf); + + if (!str_has_suffix(filebuf, PKG_CONFIG_EXT)) + continue; + + PKGCONF_TRACE(client, "trying file [%s]", filebuf); + + pkg = pkgconf_pkg_new_from_path(client, filebuf, 0); + if (pkg != NULL) + { + if (func(pkg, data)) + { + outpkg = pkg; + goto out; + } + + pkgconf_pkg_unref(client, pkg); + } + } + +out: + closedir(dir); + return outpkg; +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_scan_all(pkgconf_client_t *client, void *data, pkgconf_pkg_iteration_func_t func) + * + * Iterates over all packages found in the `package directory list`, running ``func`` on them. If ``func`` returns true, + * then stop iteration and return the last iterated package. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param void* data: An opaque pointer to data to provide the iteration function with. + * :param pkgconf_pkg_iteration_func_t func: A function which is called for each package to determine if the package matches, + * always return ``false`` to iterate over all packages. + * :return: A package object reference if one is found by the scan function, else ``NULL``. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_scan_all(pkgconf_client_t *client, void *data, pkgconf_pkg_iteration_func_t func) +{ + pkgconf_node_t *n; + pkgconf_pkg_t *pkg; + + PKGCONF_TRACE(client, "scanning preloaded list"); + PKGCONF_FOREACH_LIST_ENTRY(client->preloaded_pkgs.head, n) + { + pkg = n->data; + + /* add an additional reference to ensure preloaded packages have the same + * object ownership semantics as non-preloaded packages + */ + pkgconf_pkg_ref(client, pkg); + + if (func(pkg, data)) + return pkg; + + pkgconf_pkg_unref(client, pkg); + } + + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) + { + pkgconf_path_t *pnode = n->data; + + PKGCONF_TRACE(client, "scanning directory: %s", pnode->path); + + if ((pkg = pkgconf_pkg_scan_dir(client, pnode->path, data, func)) != NULL) + return pkg; + } + + return NULL; +} + +static pkgconf_pkg_t * +search_preload_list(pkgconf_client_t *client, const char *name) +{ + pkgconf_node_t *n; + + PKGCONF_FOREACH_LIST_ENTRY(client->preloaded_pkgs.head, n) + { + pkgconf_pkg_t *pkg = n->data; + + if (!strcmp(pkg->id, name)) + { + pkgconf_pkg_ref(client, pkg); + return pkg; + } + } + + return NULL; +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_find(pkgconf_client_t *client, const char *name) + * + * Search for a package. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param char* name: The name of the package `atom` to use for searching. + * :return: A package object reference if the package was found, else ``NULL``. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_pkg_find(pkgconf_client_t *client, const char *name) +{ + pkgconf_pkg_t *pkg = NULL; + pkgconf_node_t *n; + + PKGCONF_TRACE(client, "looking for: %s", name); + + /* name might actually be a filename. */ + if (str_has_suffix(name, PKG_CONFIG_EXT)) + { + if (client->unveil_handler != NULL) + client->unveil_handler(client, name, "r"); + + pkg = pkgconf_pkg_new_from_path(client, name, 0); + if (pkg != NULL) + { + PKGCONF_TRACE(client, "%s is a file", name); + + if (client->unveil_handler != NULL) + client->unveil_handler(client, pkg->pc_filedir, "r"); + + pkgconf_path_add(pkg->pc_filedir, &client->dir_list, true); + goto out; + } + } + + /* check builtins */ + if ((pkg = pkgconf_builtin_pkg_get(name)) != NULL) + { + PKGCONF_TRACE(client, "%s is a builtin", name); + return pkg; + } + + /* check cache */ + if (!(client->flags & PKGCONF_PKG_PKGF_NO_CACHE)) + { + if ((pkg = pkgconf_cache_lookup(client, name)) != NULL) + { + PKGCONF_TRACE(client, "%s is cached", name); + return pkg; + } + } + + /* check preload list */ + if ((pkg = search_preload_list(client, name)) != NULL) + { + PKGCONF_TRACE(client, "%s is preloaded", name); + return pkg; + } + + PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n) + { + pkgconf_path_t *pnode = n->data; + + pkg = pkgconf_pkg_try_specific_path(client, pnode->path, name); + if (pkg != NULL) + goto out; + } + +out: + pkgconf_cache_add(client, pkg); + + return pkg; +} + +/* + * !doc + * + * .. c:function:: int pkgconf_compare_version(const char *a, const char *b) + * + * Compare versions using RPM version comparison rules as described in the LSB. + * + * :param char* a: The first version to compare in the pair. + * :param char* b: The second version to compare in the pair. + * :return: -1 if the first version is less than, 0 if both versions are equal, 1 if the second version is less than. + * :rtype: int + */ +int +pkgconf_compare_version(const char *a, const char *b) +{ + char oldch1, oldch2; + char buf1[PKGCONF_ITEM_SIZE], buf2[PKGCONF_ITEM_SIZE]; + char *str1, *str2; + char *one, *two; + int ret; + bool isnum; + + /* optimization: if version matches then it's the same version. */ + if (a == NULL) + return -1; + + if (b == NULL) + return 1; + + if (!strcasecmp(a, b)) + return 0; + + pkgconf_strlcpy(buf1, a, sizeof buf1); + pkgconf_strlcpy(buf2, b, sizeof buf2); + + one = buf1; + two = buf2; + + while (*one || *two) + { + while (*one && !isalnum((unsigned char)*one) && *one != '~') + one++; + while (*two && !isalnum((unsigned char)*two) && *two != '~') + two++; + + if (*one == '~' || *two == '~') + { + if (*one != '~') + return 1; + if (*two != '~') + return -1; + + one++; + two++; + continue; + } + + if (!(*one && *two)) + break; + + str1 = one; + str2 = two; + + if (isdigit((unsigned char)*str1)) + { + while (*str1 && isdigit((unsigned char)*str1)) + str1++; + + while (*str2 && isdigit((unsigned char)*str2)) + str2++; + + isnum = true; + } + else + { + while (*str1 && isalpha((unsigned char)*str1)) + str1++; + + while (*str2 && isalpha((unsigned char)*str2)) + str2++; + + isnum = false; + } + + oldch1 = *str1; + oldch2 = *str2; + + *str1 = '\0'; + *str2 = '\0'; + + if (one == str1) + return -1; + + if (two == str2) + return (isnum ? 1 : -1); + + if (isnum) + { + int onelen, twolen; + + while (*one == '0') + one++; + + while (*two == '0') + two++; + + onelen = strlen(one); + twolen = strlen(two); + + if (onelen > twolen) + return 1; + else if (twolen > onelen) + return -1; + } + + ret = strcmp(one, two); + if (ret != 0) + return ret < 0 ? -1 : 1; + + *str1 = oldch1; + *str2 = oldch2; + + one = str1; + two = str2; + } + + if ((!*one) && (!*two)) + return 0; + + if (!*one) + return -1; + + return 1; +} + +static pkgconf_pkg_t pkg_config_virtual = { + .id = "pkg-config", + .realname = "pkg-config", + .description = "virtual package defining pkg-config API version supported", + .url = PACKAGE_BUGREPORT, + .version = PACKAGE_VERSION, + .flags = PKGCONF_PKG_PROPF_STATIC, + .vars = { + .head = &(pkgconf_node_t){ + .next = &(pkgconf_node_t){ + .next = &(pkgconf_node_t){ + .data = &(pkgconf_tuple_t){ + .key = "pc_system_libdirs", + .value = SYSTEM_LIBDIR, + } + }, + .data = &(pkgconf_tuple_t){ + .key = "pc_system_includedirs", + .value = SYSTEM_INCLUDEDIR, + } + }, + .data = &(pkgconf_tuple_t){ + .key = "pc_path", + .value = PKG_DEFAULT_PATH, + }, + }, + .tail = NULL, + } +}; + +static pkgconf_pkg_t pkgconf_virtual = { + .id = "pkgconf", + .realname = "pkgconf", + .description = "virtual package defining pkgconf API version supported", + .url = PACKAGE_BUGREPORT, + .version = PACKAGE_VERSION, + .license = "ISC", + .flags = PKGCONF_PKG_PROPF_STATIC, + .vars = { + .head = &(pkgconf_node_t){ + .next = &(pkgconf_node_t){ + .next = &(pkgconf_node_t){ + .data = &(pkgconf_tuple_t){ + .key = "pc_system_libdirs", + .value = SYSTEM_LIBDIR, + } + }, + .data = &(pkgconf_tuple_t){ + .key = "pc_system_includedirs", + .value = SYSTEM_INCLUDEDIR, + } + }, + .data = &(pkgconf_tuple_t){ + .key = "pc_path", + .value = PKG_DEFAULT_PATH, + }, + }, + .tail = NULL, + }, +}; + +typedef struct { + const char *name; + pkgconf_pkg_t *pkg; +} pkgconf_builtin_pkg_pair_t; + +/* keep these in alphabetical order */ +static const pkgconf_builtin_pkg_pair_t pkgconf_builtin_pkg_pair_set[] = { + {"pkg-config", &pkg_config_virtual}, + {"pkgconf", &pkgconf_virtual}, +}; + +static int pkgconf_builtin_pkg_pair_cmp(const void *key, const void *ptr) +{ + const pkgconf_builtin_pkg_pair_t *pair = ptr; + return strcasecmp(key, pair->name); +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_builtin_pkg_get(const char *name) + * + * Looks up a built-in package. The package should not be freed or dereferenced. + * + * :param char* name: An atom corresponding to a built-in package to search for. + * :return: the built-in package if present, else ``NULL``. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_builtin_pkg_get(const char *name) +{ + const pkgconf_builtin_pkg_pair_t *pair = bsearch(name, pkgconf_builtin_pkg_pair_set, + PKGCONF_ARRAY_SIZE(pkgconf_builtin_pkg_pair_set), sizeof(pkgconf_builtin_pkg_pair_t), + pkgconf_builtin_pkg_pair_cmp); + + return (pair != NULL) ? pair->pkg : NULL; +} + +typedef bool (*pkgconf_vercmp_res_func_t)(const char *a, const char *b); + +typedef struct { + const char *name; + pkgconf_pkg_comparator_t compare; +} pkgconf_pkg_comparator_pair_t; + +static const pkgconf_pkg_comparator_pair_t pkgconf_pkg_comparator_names[] = { + {"!=", PKGCONF_CMP_NOT_EQUAL}, + {"(any)", PKGCONF_CMP_ANY}, + {"<", PKGCONF_CMP_LESS_THAN}, + {"<=", PKGCONF_CMP_LESS_THAN_EQUAL}, + {"=", PKGCONF_CMP_EQUAL}, + {">", PKGCONF_CMP_GREATER_THAN}, + {">=", PKGCONF_CMP_GREATER_THAN_EQUAL}, +}; + +static int pkgconf_pkg_comparator_pair_namecmp(const void *key, const void *ptr) +{ + const pkgconf_pkg_comparator_pair_t *pair = ptr; + return strcmp(key, pair->name); +} + +static bool pkgconf_pkg_comparator_lt(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) < 0); +} + +static bool pkgconf_pkg_comparator_gt(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) > 0); +} + +static bool pkgconf_pkg_comparator_lte(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) <= 0); +} + +static bool pkgconf_pkg_comparator_gte(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) >= 0); +} + +static bool pkgconf_pkg_comparator_eq(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) == 0); +} + +static bool pkgconf_pkg_comparator_ne(const char *a, const char *b) +{ + return (pkgconf_compare_version(a, b) != 0); +} + +static bool pkgconf_pkg_comparator_any(const char *a, const char *b) +{ + (void) a; + (void) b; + + return true; +} + +static bool pkgconf_pkg_comparator_none(const char *a, const char *b) +{ + (void) a; + (void) b; + + return false; +} + +static const pkgconf_vercmp_res_func_t pkgconf_pkg_comparator_impls[] = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_any, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_eq, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_ne, +}; + +/* + * !doc + * + * .. c:function:: const char *pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep) + * + * Returns the comparator used in a depgraph dependency node as a string. + * + * :param pkgconf_dependency_t* pkgdep: The depgraph dependency node to return the comparator for. + * :return: A string matching the comparator or ``"???"``. + * :rtype: char * + */ +const char * +pkgconf_pkg_get_comparator(const pkgconf_dependency_t *pkgdep) +{ + if (pkgdep->compare >= PKGCONF_ARRAY_SIZE(pkgconf_pkg_comparator_names)) + return "???"; + + return pkgconf_pkg_comparator_names[pkgdep->compare].name; +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_comparator_t pkgconf_pkg_comparator_lookup_by_name(const char *name) + * + * Look up the appropriate comparator bytecode in the comparator set (defined + * in ``pkg.c``, see ``pkgconf_pkg_comparator_names`` and ``pkgconf_pkg_comparator_impls``). + * + * :param char* name: The comparator to look up by `name`. + * :return: The comparator bytecode if found, else ``PKGCONF_CMP_ANY``. + * :rtype: pkgconf_pkg_comparator_t + */ +pkgconf_pkg_comparator_t +pkgconf_pkg_comparator_lookup_by_name(const char *name) +{ + const pkgconf_pkg_comparator_pair_t *p = bsearch(name, pkgconf_pkg_comparator_names, + PKGCONF_ARRAY_SIZE(pkgconf_pkg_comparator_names), sizeof(pkgconf_pkg_comparator_pair_t), + pkgconf_pkg_comparator_pair_namecmp); + + return (p != NULL) ? p->compare : PKGCONF_CMP_ANY; +} + +typedef struct { + pkgconf_dependency_t *pkgdep; +} pkgconf_pkg_scan_providers_ctx_t; + +typedef struct { + const pkgconf_vercmp_res_func_t rulecmp[PKGCONF_CMP_COUNT]; + const pkgconf_vercmp_res_func_t depcmp[PKGCONF_CMP_COUNT]; +} pkgconf_pkg_provides_vermatch_rule_t; + +static const pkgconf_pkg_provides_vermatch_rule_t pkgconf_pkg_provides_vermatch_rules[] = { + [PKGCONF_CMP_ANY] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + }, + .depcmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + }, + }, + [PKGCONF_CMP_LESS_THAN] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + }, + .depcmp = { + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_gte, + }, + }, + [PKGCONF_CMP_GREATER_THAN] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + }, + .depcmp = { + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_lte, + }, + }, + [PKGCONF_CMP_LESS_THAN_EQUAL] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + }, + .depcmp = { + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_gt, + }, + }, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + }, + .depcmp = { + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_lt, + }, + }, + [PKGCONF_CMP_EQUAL] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_eq, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_ne + }, + .depcmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + }, + }, + [PKGCONF_CMP_NOT_EQUAL] = { + .rulecmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + [PKGCONF_CMP_LESS_THAN] = pkgconf_pkg_comparator_gte, + [PKGCONF_CMP_GREATER_THAN] = pkgconf_pkg_comparator_lte, + [PKGCONF_CMP_LESS_THAN_EQUAL] = pkgconf_pkg_comparator_gt, + [PKGCONF_CMP_GREATER_THAN_EQUAL] = pkgconf_pkg_comparator_lt, + [PKGCONF_CMP_EQUAL] = pkgconf_pkg_comparator_ne, + [PKGCONF_CMP_NOT_EQUAL] = pkgconf_pkg_comparator_eq + }, + .depcmp = { + [PKGCONF_CMP_ANY] = pkgconf_pkg_comparator_none, + }, + }, +}; + +/* + * pkgconf_pkg_scan_provides_vercmp(pkgdep, provider) + * + * compare a provides node against the requested dependency node. + * + * XXX: maybe handle PKGCONF_CMP_ANY in a versioned comparison + */ +static bool +pkgconf_pkg_scan_provides_vercmp(const pkgconf_dependency_t *pkgdep, const pkgconf_dependency_t *provider) +{ + const pkgconf_pkg_provides_vermatch_rule_t *rule = &pkgconf_pkg_provides_vermatch_rules[pkgdep->compare]; + + if (rule->depcmp[provider->compare] != NULL && + !rule->depcmp[provider->compare](provider->version, pkgdep->version)) + return false; + + if (rule->rulecmp[provider->compare] != NULL && + !rule->rulecmp[provider->compare](pkgdep->version, provider->version)) + return false; + + return true; +} + +/* + * pkgconf_pkg_scan_provides_entry(pkg, ctx) + * + * attempt to match a single package's Provides rules against the requested dependency node. + */ +static bool +pkgconf_pkg_scan_provides_entry(const pkgconf_pkg_t *pkg, const pkgconf_pkg_scan_providers_ctx_t *ctx) +{ + const pkgconf_dependency_t *pkgdep = ctx->pkgdep; + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node) + { + const pkgconf_dependency_t *provider = node->data; + if (!strcmp(provider->package, pkgdep->package)) + return pkgconf_pkg_scan_provides_vercmp(pkgdep, provider); + } + + return false; +} + +/* + * pkgconf_pkg_scan_providers(client, pkgdep, eflags) + * + * scan all available packages to see if a Provides rule matches the pkgdep. + */ +static pkgconf_pkg_t * +pkgconf_pkg_scan_providers(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags) +{ + pkgconf_pkg_t *pkg; + pkgconf_pkg_scan_providers_ctx_t ctx = { + .pkgdep = pkgdep, + }; + + pkg = pkgconf_scan_all(client, &ctx, (pkgconf_pkg_iteration_func_t) pkgconf_pkg_scan_provides_entry); + if (pkg != NULL) + { + pkgdep->match = pkgconf_pkg_ref(client, pkg); + return pkg; + } + + if (eflags != NULL) + *eflags |= PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND; + + return NULL; +} + +/* + * !doc + * + * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags) + * + * Verify a pkgconf_dependency_t node in the depgraph. If the dependency is solvable, + * return the appropriate ``pkgconf_pkg_t`` object, else ``NULL``. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_dependency_t* pkgdep: The dependency graph node to solve. + * :param uint* eflags: An optional pointer that, if set, will be populated with an error code from the resolver. + * :return: On success, the appropriate ``pkgconf_pkg_t`` object to solve the dependency, else ``NULL``. + * :rtype: pkgconf_pkg_t * + */ +pkgconf_pkg_t * +pkgconf_pkg_verify_dependency(pkgconf_client_t *client, pkgconf_dependency_t *pkgdep, unsigned int *eflags) +{ + pkgconf_pkg_t *pkg = NULL; + + if (eflags != NULL) + *eflags = PKGCONF_PKG_ERRF_OK; + + PKGCONF_TRACE(client, "trying to verify dependency: %s", pkgdep->package); + + if (pkgdep->match != NULL) + { + PKGCONF_TRACE(client, "cached dependency: %s -> %s@%p", pkgdep->package, pkgdep->match->id, pkgdep->match); + return pkgconf_pkg_ref(client, pkgdep->match); + } + + pkg = pkgconf_pkg_find(client, pkgdep->package); + if (pkg == NULL) + { + if (client->flags & PKGCONF_PKG_PKGF_SKIP_PROVIDES) + { + if (eflags != NULL) + *eflags |= PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND; + + return NULL; + } + + pkg = pkgconf_pkg_scan_providers(client, pkgdep, eflags); + } + else + { + if (pkg->id == NULL) + pkg->id = strdup(pkgdep->package); + + if (pkgconf_pkg_comparator_impls[pkgdep->compare](pkg->version, pkgdep->version) != true) + { + if (eflags != NULL) + *eflags |= PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH; + } + else + pkgdep->match = pkgconf_pkg_ref(client, pkg); + } + + if (pkg != NULL && pkg->why == NULL) + pkg->why = strdup(pkgdep->package); + + return pkg; +} + +/* + * !doc + * + * .. c:function:: unsigned int pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth) + * + * Verify the graph dependency nodes are satisfiable by walking the tree using + * ``pkgconf_pkg_traverse()``. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* root: The root entry in the package dependency graph which should contain the top-level dependencies to resolve. + * :param int depth: The maximum allowed depth for dependency resolution. + * :return: On success, ``PKGCONF_PKG_ERRF_OK`` (0), else an error code. + * :rtype: unsigned int + */ +unsigned int +pkgconf_pkg_verify_graph(pkgconf_client_t *client, pkgconf_pkg_t *root, int depth) +{ + return pkgconf_pkg_traverse(client, root, NULL, NULL, depth, 0); +} + +static unsigned int +pkgconf_pkg_report_graph_error(pkgconf_client_t *client, pkgconf_pkg_t *parent, pkgconf_pkg_t *pkg, pkgconf_dependency_t *node, unsigned int eflags) +{ + if (eflags & PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND) + { + if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) & !client->already_sent_notice) + { + pkgconf_error(client, "Package %s was not found in the pkg-config search path.\n", node->package); + pkgconf_error(client, "Perhaps you should add the directory containing `%s.pc'\n", node->package); + pkgconf_error(client, "to the PKG_CONFIG_PATH environment variable\n"); + client->already_sent_notice = true; + } + + if (parent->flags & PKGCONF_PKG_PROPF_VIRTUAL) + pkgconf_error(client, "Package '%s' not found\n", node->package); + else + pkgconf_error(client, "Package '%s', required by '%s', not found\n", node->package, parent->id); + + pkgconf_audit_log(client, "%s NOT-FOUND\n", node->package); + } + else if (eflags & PKGCONF_PKG_ERRF_PACKAGE_VER_MISMATCH) + { + pkgconf_error(client, "Package dependency requirement '%s %s %s' could not be satisfied.\n", + node->package, pkgconf_pkg_get_comparator(node), node->version); + + if (pkg != NULL) + pkgconf_error(client, "Package '%s' has version '%s', required version is '%s %s'\n", + node->package, pkg->version, pkgconf_pkg_get_comparator(node), node->version); + } + + if (pkg != NULL) + pkgconf_pkg_unref(client, pkg); + + return eflags; +} + +static inline unsigned int +pkgconf_pkg_walk_list(pkgconf_client_t *client, + pkgconf_pkg_t *parent, + pkgconf_list_t *deplist, + pkgconf_pkg_traverse_func_t func, + void *data, + int depth, + unsigned int skip_flags) +{ + unsigned int eflags = PKGCONF_PKG_ERRF_OK; + pkgconf_node_t *node, *next; + + parent->flags |= PKGCONF_PKG_PROPF_ANCESTOR; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(deplist->head, next, node) + { + unsigned int eflags_local = PKGCONF_PKG_ERRF_OK; + pkgconf_dependency_t *depnode = node->data; + pkgconf_pkg_t *pkgdep; + + if (*depnode->package == '\0') + continue; + + pkgdep = pkgconf_pkg_verify_dependency(client, depnode, &eflags_local); + + eflags |= eflags_local; + if (eflags_local != PKGCONF_PKG_ERRF_OK && !(client->flags & PKGCONF_PKG_PKGF_SKIP_ERRORS)) + { + pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local); + continue; + } + if (pkgdep == NULL) + continue; + + if((pkgdep->flags & PKGCONF_PKG_PROPF_ANCESTOR) != 0) + { + /* In this case we have a circular reference. + * We break that by deleteing the circular node from the + * the list, so that we dont create a situation where + * memory is leaked due to circular ownership. + * i.e: A owns B owns A + * + * TODO(ariadne): Breaking circular references between Requires and Requires.private + * lists causes problems. Find a way to refactor the Requires.private list out. + */ + if (!(depnode->flags & PKGCONF_PKG_DEPF_PRIVATE) && + !(parent->flags & PKGCONF_PKG_PROPF_VIRTUAL)) + { + pkgconf_warn(client, "%s: breaking circular reference (%s -> %s -> %s)\n", + parent->id, parent->id, pkgdep->id, parent->id); + + pkgconf_node_delete(node, deplist); + pkgconf_dependency_unref(client, depnode); + } + + goto next; + } + + if (skip_flags && (depnode->flags & skip_flags) == skip_flags) + goto next; + + pkgconf_audit_log_dependency(client, pkgdep, depnode); + + eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags); +next: + pkgconf_pkg_unref(client, pkgdep); + } + + parent->flags &= ~PKGCONF_PKG_PROPF_ANCESTOR; + + return eflags; +} + +static inline unsigned int +pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client, + pkgconf_pkg_t *root, pkgconf_list_t *deplist) +{ + unsigned int eflags; + pkgconf_node_t *node, *childnode; + + PKGCONF_FOREACH_LIST_ENTRY(deplist->head, node) + { + pkgconf_dependency_t *parentnode = node->data; + + if (*parentnode->package == '\0') + continue; + + PKGCONF_FOREACH_LIST_ENTRY(root->required.head, childnode) + { + pkgconf_pkg_t *pkgdep; + pkgconf_dependency_t *depnode = childnode->data; + + if (*depnode->package == '\0' || strcmp(depnode->package, parentnode->package)) + continue; + + pkgdep = pkgconf_pkg_verify_dependency(client, parentnode, &eflags); + if (eflags == PKGCONF_PKG_ERRF_OK) + { + pkgconf_error(client, "Version '%s' of '%s' conflicts with '%s' due to satisfying conflict rule '%s %s%s%s'.\n", + pkgdep->version, pkgdep->realname, root->realname, parentnode->package, pkgconf_pkg_get_comparator(parentnode), + parentnode->version != NULL ? " " : "", parentnode->version != NULL ? parentnode->version : ""); + + if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS)) + { + pkgconf_error(client, "It may be possible to ignore this conflict and continue, try the\n"); + pkgconf_error(client, "PKG_CONFIG_IGNORE_CONFLICTS environment variable.\n"); + } + + pkgconf_pkg_unref(client, pkgdep); + + return PKGCONF_PKG_ERRF_PACKAGE_CONFLICT; + } + + pkgconf_pkg_unref(client, pkgdep); + } + } + + return PKGCONF_PKG_ERRF_OK; +} + +/* + * !doc + * + * .. c:function:: unsigned int pkgconf_pkg_traverse(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_pkg_traverse_func_t func, void *data, int maxdepth, unsigned int skip_flags) + * + * Walk and resolve the dependency graph up to `maxdepth` levels. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* root: The root of the dependency graph. + * :param pkgconf_pkg_traverse_func_t func: A traversal function to call for each resolved node in the dependency graph. + * :param void* data: An opaque pointer to data to be passed to the traversal function. + * :param int maxdepth: The maximum depth to walk the dependency graph for. -1 means infinite recursion. + * :param uint skip_flags: Skip over dependency nodes containing the specified flags. A setting of 0 skips no dependency nodes. + * :return: ``PKGCONF_PKG_ERRF_OK`` on success, else an error code. + * :rtype: unsigned int + */ +static unsigned int +pkgconf_pkg_traverse_main(pkgconf_client_t *client, + pkgconf_pkg_t *root, + pkgconf_pkg_traverse_func_t func, + void *data, + int maxdepth, + unsigned int skip_flags) +{ + unsigned int eflags = PKGCONF_PKG_ERRF_OK; + + if (maxdepth == 0) + return eflags; + + /* Short-circuit if we have already visited this node. + */ + if (root->serial == client->serial) + return eflags; + + root->serial = client->serial; + + if (root->identifier == 0) + root->identifier = ++client->identifier; + + PKGCONF_TRACE(client, "%s: level %d, serial %"PRIu64, root->id, maxdepth, client->serial); + + if ((root->flags & PKGCONF_PKG_PROPF_VIRTUAL) != PKGCONF_PKG_PROPF_VIRTUAL || (client->flags & PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL) != PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL) + { + if (func != NULL) + func(client, root, data); + } + + if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS) && root->conflicts.head != NULL) + { + PKGCONF_TRACE(client, "%s: walking 'Conflicts' list", root->id); + + eflags = pkgconf_pkg_walk_conflicts_list(client, root, &root->conflicts); + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + } + + PKGCONF_TRACE(client, "%s: walking 'Requires' list", root->id); + eflags = pkgconf_pkg_walk_list(client, root, &root->required, func, data, maxdepth, skip_flags); + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + + PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id); + + /* XXX: ugly */ + client->flags |= PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; + eflags = pkgconf_pkg_walk_list(client, root, &root->requires_private, func, data, maxdepth, skip_flags); + client->flags &= ~PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; + + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + + return eflags; +} + +unsigned int +pkgconf_pkg_traverse(pkgconf_client_t *client, + pkgconf_pkg_t *root, + pkgconf_pkg_traverse_func_t func, + void *data, + int maxdepth, + unsigned int skip_flags) +{ + if (root->flags & PKGCONF_PKG_PROPF_VIRTUAL) + client->serial++; + + if ((client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) == 0) + skip_flags |= PKGCONF_PKG_DEPF_PRIVATE; + + return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags); +} + +static void +pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_list_t *list = data; + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy(client, list, frag, false); + } +} + +static void +pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_list_t *list = data; + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_private.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy(client, list, frag, true); + } +} + +/* + * !doc + * + * .. c:function:: int pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) + * + * Walks a dependency graph and extracts relevant ``CFLAGS`` fragments. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* root: The root of the dependency graph. + * :param pkgconf_list_t* list: The fragment list to add the extracted ``CFLAGS`` fragments to. + * :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion. + * :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code. + * :rtype: unsigned int + */ +unsigned int +pkgconf_pkg_cflags(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) +{ + unsigned int eflag; + unsigned int skip_flags = (client->flags & PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS) == 0 ? PKGCONF_PKG_DEPF_INTERNAL : 0; + pkgconf_list_t frags = PKGCONF_LIST_INITIALIZER; + + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &frags, maxdepth, skip_flags); + + if (eflag == PKGCONF_PKG_ERRF_OK && client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, &frags, maxdepth, skip_flags); + + if (eflag != PKGCONF_PKG_ERRF_OK) + { + pkgconf_fragment_free(&frags); + return eflag; + } + + pkgconf_fragment_copy_list(client, list, &frags); + pkgconf_fragment_free(&frags); + + return eflag; +} + +static void +pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + pkgconf_list_t *list = data; + pkgconf_node_t *node; + + if (!(client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) && pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + return; + + PKGCONF_FOREACH_LIST_ENTRY(pkg->libs.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy(client, list, frag, (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE) != 0); + } + + if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS) + { + PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_private.head, node) + { + pkgconf_fragment_t *frag = node->data; + pkgconf_fragment_copy(client, list, frag, true); + } + } +} + +/* + * !doc + * + * .. c:function:: int pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) + * + * Walks a dependency graph and extracts relevant ``LIBS`` fragments. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* root: The root of the dependency graph. + * :param pkgconf_list_t* list: The fragment list to add the extracted ``LIBS`` fragments to. + * :param int maxdepth: The maximum allowed depth for dependency resolution. -1 means infinite recursion. + * :return: ``PKGCONF_PKG_ERRF_OK`` if successful, otherwise an error code. + * :rtype: unsigned int + */ +unsigned int +pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth) +{ + unsigned int eflag; + + eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, list, maxdepth, 0); + + if (eflag != PKGCONF_PKG_ERRF_OK) + { + pkgconf_fragment_free(list); + return eflag; + } + + return eflag; +} diff --git a/libpkgconf/queue.c b/libpkgconf/queue.c new file mode 100644 index 00000000000..09c02e368f8 --- /dev/null +++ b/libpkgconf/queue.c @@ -0,0 +1,408 @@ +/* + * queue.c + * compilation of a list of packages into a world dependency set + * + * Copyright (c) 2012 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `queue` module + * ========================= + * + * The `queue` module provides an interface that allows easily building a dependency graph from an + * arbitrary set of dependencies. It also provides support for doing "preflight" checks on the entire + * dependency graph prior to working with it. + * + * Using the `queue` module functions is the recommended way of working with dependency graphs. + */ + +/* + * !doc + * + * .. c:function:: void pkgconf_queue_push(pkgconf_list_t *list, const char *package) + * + * Pushes a requested dependency onto the dependency resolver's queue. + * + * :param pkgconf_list_t* list: the dependency resolution queue to add the package request to. + * :param char* package: the dependency atom requested + * :return: nothing + */ +void +pkgconf_queue_push(pkgconf_list_t *list, const char *package) +{ + pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t)); + + pkgq->package = strdup(package); + pkgconf_node_insert_tail(&pkgq->iter, pkgq, list); +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list) + * + * Compile a dependency resolution queue into a dependency resolution problem if possible, otherwise report an error. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* world: The designated root of the dependency graph. + * :param pkgconf_list_t* list: The list of dependency requests to consider. + * :return: true if the built dependency resolution problem is consistent, else false + * :rtype: bool + */ +bool +pkgconf_queue_compile(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list) +{ + pkgconf_node_t *iter; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, iter) + { + pkgconf_queue_t *pkgq; + + pkgq = iter->data; + pkgconf_dependency_parse(client, world, &world->required, pkgq->package, PKGCONF_PKG_DEPF_QUERY); + } + + return (world->required.head != NULL); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_queue_free(pkgconf_list_t *list) + * + * Release any memory related to a dependency resolution queue. + * + * :param pkgconf_list_t* list: The dependency resolution queue to release. + * :return: nothing + */ +void +pkgconf_queue_free(pkgconf_list_t *list) +{ + pkgconf_node_t *node, *tnode; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, tnode, node) + { + pkgconf_queue_t *pkgq = node->data; + + free(pkgq->package); + free(pkgq); + } +} + +static void +pkgconf_queue_mark_public(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data) +{ + if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) + { + pkgconf_list_t *list = data; + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + pkgconf_dependency_t *dep = node->data; + if (dep->match == pkg) + dep->flags &= ~PKGCONF_PKG_DEPF_PRIVATE; + } + + pkg->flags &= ~PKGCONF_PKG_PROPF_VISITED_PRIVATE; + + PKGCONF_TRACE(client, "%s: updated, public", pkg->id); + } +} + +static unsigned int +pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client, + pkgconf_pkg_t *root, + void *data, + int maxdepth); + +static inline unsigned int +pkgconf_queue_collect_dependencies_walk(pkgconf_client_t *client, + pkgconf_list_t *deplist, + void *data, + int depth) +{ + unsigned int eflags = PKGCONF_PKG_ERRF_OK; + pkgconf_node_t *node; + pkgconf_pkg_t *world = data; + + PKGCONF_FOREACH_LIST_ENTRY_REVERSE(deplist->tail, node) + { + pkgconf_dependency_t *dep = node->data; + pkgconf_dependency_t *flattened_dep; + pkgconf_pkg_t *pkg = dep->match; + + if (*dep->package == '\0') + continue; + + if (pkg == NULL) + { + PKGCONF_TRACE(client, "WTF: unmatched dependency %p <%s>", dep, dep->package); + continue; + } + + if (pkg->serial == client->serial) + continue; + + if (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE) + pkg->flags |= PKGCONF_PKG_PROPF_VISITED_PRIVATE; + else + pkg->flags &= ~PKGCONF_PKG_PROPF_VISITED_PRIVATE; + + eflags |= pkgconf_queue_collect_dependencies_main(client, pkg, data, depth - 1); + + flattened_dep = pkgconf_dependency_copy(client, dep); + pkgconf_node_insert(&flattened_dep->iter, flattened_dep, &world->required); + } + + return eflags; +} + +static unsigned int +pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client, + pkgconf_pkg_t *root, + void *data, + int maxdepth) +{ + unsigned int eflags = PKGCONF_PKG_ERRF_OK; + + if (maxdepth == 0) + return eflags; + + /* Short-circuit if we have already visited this node. + */ + if (root->serial == client->serial) + return eflags; + + root->serial = client->serial; + + PKGCONF_TRACE(client, "%s: collecting private dependencies, level %d", root->id, maxdepth); + + /* XXX: ugly */ + const unsigned int saved_flags = client->flags; + client->flags |= PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; + eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_private, data, maxdepth); + client->flags = saved_flags; + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + + PKGCONF_TRACE(client, "%s: collecting public dependencies, level %d", root->id, maxdepth); + + eflags = pkgconf_queue_collect_dependencies_walk(client, &root->required, data, maxdepth); + if (eflags != PKGCONF_PKG_ERRF_OK) + return eflags; + + PKGCONF_TRACE(client, "%s: finished, %s", root->id, (root->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) ? "private" : "public"); + + return eflags; +} + +static inline unsigned int +pkgconf_queue_collect_dependencies(pkgconf_client_t *client, + pkgconf_pkg_t *root, + void *data, + int maxdepth) +{ + ++client->serial; + return pkgconf_queue_collect_dependencies_main(client, root, data, maxdepth); +} + +static inline unsigned int +pkgconf_queue_verify(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth) +{ + unsigned int result; + const unsigned int saved_flags = client->flags; + pkgconf_pkg_t initial_world = { + .id = "user:request", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + if (!pkgconf_queue_compile(client, &initial_world, list)) + { + pkgconf_solution_free(client, &initial_world); + return PKGCONF_PKG_ERRF_DEPGRAPH_BREAK; + } + + PKGCONF_TRACE(client, "solving"); + result = pkgconf_pkg_traverse(client, &initial_world, NULL, NULL, maxdepth, 0); + if (result != PKGCONF_PKG_ERRF_OK) + { + pkgconf_solution_free(client, &initial_world); + return result; + } + + PKGCONF_TRACE(client, "flattening"); + result = pkgconf_queue_collect_dependencies(client, &initial_world, world, maxdepth); + if (result != PKGCONF_PKG_ERRF_OK) + { + pkgconf_solution_free(client, &initial_world); + return result; + } + + if (client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) + { + PKGCONF_TRACE(client, "marking public deps"); + client->flags &= ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + client->flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS; + result = pkgconf_pkg_traverse(client, &initial_world, pkgconf_queue_mark_public, &world->required, maxdepth, 0); + client->flags = saved_flags; + if (result != PKGCONF_PKG_ERRF_OK) + { + pkgconf_solution_free(client, &initial_world); + return result; + } + } + + /* free the initial solution */ + pkgconf_solution_free(client, &initial_world); + + return PKGCONF_PKG_ERRF_OK; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_solution_free(pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth) + * + * Removes references to package nodes contained in a solution. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_pkg_t* world: The root for the generated dependency graph. Should have PKGCONF_PKG_PROPF_VIRTUAL flag. + * :returns: nothing + */ +void +pkgconf_solution_free(pkgconf_client_t *client, pkgconf_pkg_t *world) +{ + (void) client; + + if (world->flags & PKGCONF_PKG_PROPF_VIRTUAL) + { + pkgconf_dependency_free(&world->required); + pkgconf_dependency_free(&world->requires_private); + } +} + +/* + * !doc + * + * .. c:function:: bool pkgconf_queue_solve(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_pkg_t *world, int maxdepth) + * + * Solves and flattens the dependency graph for the supplied dependency list. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_list_t* list: The list of dependency requests to consider. + * :param pkgconf_pkg_t* world: The root for the generated dependency graph, provided by the caller. Should have PKGCONF_PKG_PROPF_VIRTUAL flag. + * :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + * :returns: true if the dependency resolver found a solution, otherwise false. + * :rtype: bool + */ +bool +pkgconf_queue_solve(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_pkg_t *world, int maxdepth) +{ + /* if maxdepth is one, then we will not traverse deeper than our virtual package. */ + if (!maxdepth) + maxdepth = -1; + + unsigned int flags = client->flags; + client->flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE; + + unsigned int ret = pkgconf_queue_verify(client, world, list, maxdepth); + client->flags = flags; + + return ret == PKGCONF_PKG_ERRF_OK; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data) + * + * Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem and + * feed the solution to a callback function if a complete dependency graph is found. + * + * This function should not be used in new code. Use pkgconf_queue_solve instead. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_list_t* list: The list of dependency requests to consider. + * :param pkgconf_queue_apply_func_t func: The callback function to call if a solution is found by the dependency resolver. + * :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + * :param void* data: An opaque pointer which is passed to the callback function. + * :returns: true if the dependency resolver found a solution, otherwise false. + * :rtype: bool + */ +bool +pkgconf_queue_apply(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data) +{ + bool ret = false; + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + /* if maxdepth is one, then we will not traverse deeper than our virtual package. */ + if (!maxdepth) + maxdepth = -1; + + if (!pkgconf_queue_solve(client, list, &world, maxdepth)) + goto cleanup; + + /* the world dependency set is flattened after it is returned from pkgconf_queue_verify */ + if (!func(client, &world, data, maxdepth)) + goto cleanup; + + ret = true; + +cleanup: + pkgconf_pkg_free(client, &world); + return ret; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_queue_apply_func_t func, int maxdepth, void *data) + * + * Attempt to compile a dependency resolution queue into a dependency resolution problem, then attempt to solve the problem. + * + * :param pkgconf_client_t* client: The pkgconf client object to use for dependency resolution. + * :param pkgconf_list_t* list: The list of dependency requests to consider. + * :param int maxdepth: The maximum allowed depth for the dependency resolver. A depth of -1 means unlimited. + * :returns: true if the dependency resolver found a solution, otherwise false. + * :rtype: bool + */ +bool +pkgconf_queue_validate(pkgconf_client_t *client, pkgconf_list_t *list, int maxdepth) +{ + bool retval = true; + pkgconf_pkg_t world = { + .id = "virtual:world", + .realname = "virtual world package", + .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL, + }; + + /* if maxdepth is one, then we will not traverse deeper than our virtual package. */ + if (!maxdepth) + maxdepth = -1; + + if (pkgconf_queue_verify(client, &world, list, maxdepth) != PKGCONF_PKG_ERRF_OK) + retval = false; + + pkgconf_pkg_free(client, &world); + + return retval; +} diff --git a/libpkgconf/stdinc.h b/libpkgconf/stdinc.h new file mode 100644 index 00000000000..31284ed8826 --- /dev/null +++ b/libpkgconf/stdinc.h @@ -0,0 +1,75 @@ +/* + * stdinc.h + * pull in standard headers (including portability hacks) + * + * Copyright (c) 2012 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#ifndef LIBPKGCONF_STDINC_H +#define LIBPKGCONF_STDINC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +# define PATH_DEV_NULL "nul" +# ifdef _WIN64 +# ifndef __MINGW32__ +# define SIZE_FMT_SPECIFIER "%I64u" +# else +# define SIZE_FMT_SPECIFIER "%llu" +# endif +# else +# define SIZE_FMT_SPECIFIER "%u" +# endif +# ifndef ssize_t +# ifndef __MINGW32__ +# include +# else +# include +# endif +# define ssize_t SSIZE_T +# endif +# ifndef __MINGW32__ +# include "win-dirent.h" +# else +# include +# endif +# define PKGCONF_ITEM_SIZE (_MAX_PATH + 1024) +#else +# define PATH_DEV_NULL "/dev/null" +# define SIZE_FMT_SPECIFIER "%zu" +# ifdef __HAIKU__ +# include +# endif +# include +# include +# include +# include +# ifdef PATH_MAX +# define PKGCONF_ITEM_SIZE (PATH_MAX + 1024) +# else +# define PKGCONF_ITEM_SIZE (4096 + 1024) +# endif +#endif + +#endif diff --git a/libpkgconf/tuple.c b/libpkgconf/tuple.c new file mode 100644 index 00000000000..83f6a479e77 --- /dev/null +++ b/libpkgconf/tuple.c @@ -0,0 +1,476 @@ +/* + * tuple.c + * management of key->value tuples + * + * Copyright (c) 2011, 2012 pkgconf authors (see AUTHORS). + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include + +/* + * !doc + * + * libpkgconf `tuple` module + * ========================= + * + * The `tuple` module provides key-value mappings backed by a linked list. The key-value + * mapping is mainly used for variable substitution when parsing .pc files. + * + * There are two sets of mappings: a ``pkgconf_pkg_t`` specific mapping, and a `global` mapping. + * The `tuple` module provides convenience wrappers for managing the `global` mapping, which is + * attached to a given client object. + */ + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value) + * + * Defines a global variable, replacing the previous declaration if one was set. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :param char* key: The key for the mapping (variable name). + * :param char* value: The value for the mapped entry. + * :return: nothing + */ +void +pkgconf_tuple_add_global(pkgconf_client_t *client, const char *key, const char *value) +{ + pkgconf_tuple_add(client, &client->global_vars, key, value, false, 0); +} + +static pkgconf_tuple_t * +lookup_global_tuple(const pkgconf_client_t *client, const char *key) +{ + pkgconf_node_t *node; + + PKGCONF_FOREACH_LIST_ENTRY(client->global_vars.head, node) + { + pkgconf_tuple_t *tuple = node->data; + + if (!strcmp(tuple->key, key)) + return tuple; + } + + return NULL; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key) + * + * Looks up a global variable. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param char* key: The key or variable name to look up. + * :return: the contents of the variable or ``NULL`` + * :rtype: char * + */ +char * +pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key) +{ + pkgconf_tuple_t *tuple; + + tuple = lookup_global_tuple(client, key); + if (tuple == NULL) + return NULL; + + return tuple->value; +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free_global(pkgconf_client_t *client) + * + * Delete all global variables associated with a pkgconf client object. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :return: nothing + */ +void +pkgconf_tuple_free_global(pkgconf_client_t *client) +{ + pkgconf_tuple_free(&client->global_vars); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv) + * + * Parse and define a global variable. + * + * :param pkgconf_client_t* client: The pkgconf client object to modify. + * :param char* kv: The variable in the form of ``key=value``. + * :return: nothing + */ +void +pkgconf_tuple_define_global(pkgconf_client_t *client, const char *kv) +{ + char *workbuf = strdup(kv); + char *value; + pkgconf_tuple_t *tuple; + + value = strchr(workbuf, '='); + if (value == NULL) + goto out; + + *value++ = '\0'; + + tuple = pkgconf_tuple_add(client, &client->global_vars, workbuf, value, false, 0); + if (tuple != NULL) + tuple->flags = PKGCONF_PKG_TUPLEF_OVERRIDE; + +out: + free(workbuf); +} + +static void +pkgconf_tuple_find_delete(pkgconf_list_t *list, const char *key) +{ + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + { + pkgconf_tuple_t *tuple = node->data; + + if (!strcmp(tuple->key, key)) + { + pkgconf_tuple_free_entry(tuple, list); + return; + } + } +} + +static char * +dequote(const char *value) +{ + char *buf = calloc(1, (strlen(value) + 1) * 2); + char *bptr = buf; + const char *i; + char quote = 0; + + if (*value == '\'' || *value == '"') + quote = *value; + + for (i = value; *i != '\0'; i++) + { + if (*i == '\\' && quote && *(i + 1) == quote) + { + i++; + *bptr++ = *i; + } + else if (*i != quote) + *bptr++ = *i; + } + + return buf; +} + +static const char * +find_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars) +{ + const char *sysroot_dir; + + sysroot_dir = pkgconf_tuple_find(client, vars, "pc_sysrootdir"); + if (sysroot_dir == NULL) + sysroot_dir = client->sysroot_dir; + + return sysroot_dir; +} + +static bool +should_rewrite_sysroot(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *buf, unsigned int flags) +{ + const char *sysroot_dir; + + if (flags & PKGCONF_PKG_PROPF_UNINSTALLED && !(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES)) + return false; + + sysroot_dir = find_sysroot(client, vars); + if (sysroot_dir == NULL) + return false; + + if (*buf != '/') + return false; + + if (!strcmp(sysroot_dir, "/")) + return false; + + if (strlen(buf) <= strlen(sysroot_dir)) + return false; + + if (strstr(buf + strlen(sysroot_dir), sysroot_dir) == NULL) + return false; + + return true; +} + +/* + * !doc + * + * .. c:function:: pkgconf_tuple_t *pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse) + * + * Optionally parse and then define a variable. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param pkgconf_list_t* list: The variable list to add the new variable to. + * :param char* key: The name of the variable being added. + * :param char* value: The value of the variable being added. + * :param bool parse: Whether or not to parse the value for variable substitution. + * :return: a variable object + * :rtype: pkgconf_tuple_t * + */ +pkgconf_tuple_t * +pkgconf_tuple_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key, const char *value, bool parse, unsigned int flags) +{ + char *dequote_value; + pkgconf_tuple_t *tuple = calloc(1, sizeof(pkgconf_tuple_t)); + + pkgconf_tuple_find_delete(list, key); + + dequote_value = dequote(value); + + tuple->key = strdup(key); + if (parse) + tuple->value = pkgconf_tuple_parse(client, list, dequote_value, flags); + else + tuple->value = strdup(dequote_value); + + PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, tuple->value, parse); + + pkgconf_node_insert(&tuple->iter, tuple, list); + + free(dequote_value); + + return tuple; +} + +/* + * !doc + * + * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) + * + * Look up a variable in a variable list. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param pkgconf_list_t* list: The variable list to search. + * :param char* key: The variable name to search for. + * :return: the value of the variable or ``NULL`` + * :rtype: char * + */ +char * +pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key) +{ + pkgconf_node_t *node; + pkgconf_tuple_t *global_tuple; + + global_tuple = lookup_global_tuple(client, key); + if (global_tuple != NULL && global_tuple->flags & PKGCONF_PKG_TUPLEF_OVERRIDE) + return global_tuple->value; + + PKGCONF_FOREACH_LIST_ENTRY(list->head, node) + { + pkgconf_tuple_t *tuple = node->data; + + if (!strcmp(tuple->key, key)) + return tuple->value; + } + + if (global_tuple != NULL) + return global_tuple->value; + + return NULL; +} + +/* + * !doc + * + * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags) + * + * Parse an expression for variable substitution. + * + * :param pkgconf_client_t* client: The pkgconf client object to access. + * :param pkgconf_list_t* list: The variable list to search for variables (along side the global variable list). + * :param char* value: The ``key=value`` string to parse. + * :param uint flags: Any flags to consider while parsing. + * :return: the variable data with any variables substituted + * :rtype: char * + */ +char * +pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags) +{ + char buf[PKGCONF_BUFSIZE]; + const char *ptr; + char *bptr = buf; + + if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES) && + (!(flags & PKGCONF_PKG_PROPF_UNINSTALLED) || (client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))) + { + if (*value == '/' && client->sysroot_dir != NULL && strncmp(value, client->sysroot_dir, strlen(client->sysroot_dir))) + bptr += pkgconf_strlcpy(buf, client->sysroot_dir, sizeof buf); + } + + for (ptr = value; *ptr != '\0' && bptr - buf < PKGCONF_BUFSIZE; ptr++) + { + if (*ptr != '$' || (*ptr == '$' && *(ptr + 1) != '{')) + *bptr++ = *ptr; + else if (*(ptr + 1) == '{') + { + char varname[PKGCONF_ITEM_SIZE]; + char *vend = varname + PKGCONF_ITEM_SIZE - 1; + char *vptr = varname; + const char *pptr; + char *kv, *parsekv; + + *vptr = '\0'; + + for (pptr = ptr + 2; *pptr != '\0'; pptr++) + { + if (*pptr != '}') + { + if (vptr < vend) + *vptr++ = *pptr; + else + { + *vptr = '\0'; + break; + } + } + else + { + *vptr = '\0'; + break; + } + } + + PKGCONF_TRACE(client, "lookup tuple %s", varname); + + size_t remain = PKGCONF_BUFSIZE - (bptr - buf); + ptr += (pptr - ptr); + kv = pkgconf_tuple_find_global(client, varname); + if (kv != NULL) + { + size_t nlen = pkgconf_strlcpy(bptr, kv, remain); + if (nlen > remain) + { + pkgconf_warn(client, "warning: truncating very long variable to 64KB\n"); + + bptr = buf + (PKGCONF_BUFSIZE - 1); + break; + } + + bptr += nlen; + } + else + { + kv = pkgconf_tuple_find(client, vars, varname); + + if (kv != NULL) + { + size_t nlen; + + parsekv = pkgconf_tuple_parse(client, vars, kv, flags); + nlen = pkgconf_strlcpy(bptr, parsekv, remain); + free(parsekv); + + if (nlen > remain) + { + pkgconf_warn(client, "warning: truncating very long variable to 64KB\n"); + + bptr = buf + (PKGCONF_BUFSIZE - 1); + break; + } + + bptr += nlen; + } + } + } + } + + *bptr = '\0'; + + /* + * Sigh. Somebody actually attempted to use freedesktop.org pkg-config's broken sysroot support, + * which was written by somebody who did not understand how sysroots are supposed to work. This + * results in an incorrect path being built as the sysroot will be prepended twice, once explicitly, + * and once by variable expansion (the pkgconf approach). We could simply make ${pc_sysrootdir} blank, + * but sometimes it is necessary to know the explicit sysroot path for other reasons, so we can't really + * do that. + * + * As a result, we check to see if ${pc_sysrootdir} is prepended as a duplicate, and if so, remove the + * prepend. This allows us to handle both our approach and the broken freedesktop.org implementation's + * approach. Because a path can be shorter than ${pc_sysrootdir}, we do some checks first to ensure it's + * safe to skip ahead in the string to scan for our sysroot dir. + * + * Finally, we call pkgconf_path_relocate() to clean the path of spurious elements. + * + * New in 1.9: Only attempt to rewrite the sysroot if we are not processing an uninstalled package. + */ + if (should_rewrite_sysroot(client, vars, buf, flags)) + { + char cleanpath[PKGCONF_ITEM_SIZE]; + const char *sysroot_dir = find_sysroot(client, vars); + + pkgconf_strlcpy(cleanpath, buf + strlen(sysroot_dir), sizeof cleanpath); + pkgconf_path_relocate(cleanpath, sizeof cleanpath); + + return strdup(cleanpath); + } + + return strdup(buf); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) + * + * Deletes a variable object, removing it from any variable lists and releasing any memory associated + * with it. + * + * :param pkgconf_tuple_t* tuple: The variable object to release. + * :param pkgconf_list_t* list: The variable list the variable object is attached to. + * :return: nothing + */ +void +pkgconf_tuple_free_entry(pkgconf_tuple_t *tuple, pkgconf_list_t *list) +{ + pkgconf_node_delete(&tuple->iter, list); + + free(tuple->key); + free(tuple->value); + free(tuple); +} + +/* + * !doc + * + * .. c:function:: void pkgconf_tuple_free(pkgconf_list_t *list) + * + * Deletes a variable list and any variables attached to it. + * + * :param pkgconf_list_t* list: The variable list to delete. + * :return: nothing + */ +void +pkgconf_tuple_free(pkgconf_list_t *list) +{ + pkgconf_node_t *node, *next; + + PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node) + pkgconf_tuple_free_entry(node->data, list); + + pkgconf_list_zero(list); +} diff --git a/libpkgconf/win-dirent.h b/libpkgconf/win-dirent.h new file mode 100644 index 00000000000..0734d6dc470 --- /dev/null +++ b/libpkgconf/win-dirent.h @@ -0,0 +1,1028 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir(const char *dirname); +static _WDIR *_wopendir(const wchar_t *dirname); + +static struct dirent *readdir(DIR *dirp); +static struct _wdirent *_wreaddir(_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir(DIR *dirp); +static int _wclosedir(_WDIR *dirp); + +static void rewinddir(DIR* dirp); +static void _wrewinddir(_WDIR* dirp); + +static int scandir(const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort(const struct dirent **a, const struct dirent **b); + +static int versionsort(const struct dirent **a, const struct dirent **b); + +static int strverscmp(const char *a, const char *b); + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + +/* Compatibility with older Microsoft compilers and non-Microsoft compilers */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +# define wcstombs_s dirent_wcstombs_s +# define mbstowcs_s dirent_mbstowcs_s +#endif + +/* Optimize dirent_set_errno() away on modern Microsoft compilers */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define dirent_set_errno _set_errno +#endif + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp); + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, size_t sizeInWords, + const char *mbstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, size_t sizeInBytes, + const wchar_t *wcstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void dirent_set_errno(int error); +#endif + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR *_wopendir(const wchar_t *dirname) +{ + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + _WDIR *dirp = (_WDIR*) malloc(sizeof(struct _WDIR)); + if (!dirp) + return NULL; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL); +#else + /* WinRT */ + size_t n = wcslen(dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc(sizeof(wchar_t) * n + 16); + if (dirp->patt == NULL) + goto exit_closedir; + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW(dirname, n, dirp->patt, NULL); + if (n <= 0) + goto exit_closedir; +#else + /* WinRT */ + wcsncpy_s(dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first(dirp)) + goto exit_closedir; + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir(dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent *_wreaddir(_WDIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct _wdirent *entry; + (void) _wreaddir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) +{ + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* Return NULL to indicate end of directory */ + *result = NULL; + return /*OK*/0; + } + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + size_t n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof(struct _wdirent); + + /* Set result address */ + *result = entry; + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int _wclosedir(_WDIR *dirp) +{ + if (!dirp) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) + FindClose(dirp->handle); + + /* Release search pattern */ + free(dirp->patt); + + /* Release directory structure */ + free(dirp); + return /*success*/0; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void _wrewinddir(_WDIR* dirp) +{ + if (!dirp) + return; + + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) + FindClose(dirp->handle); + + /* Open new search handle */ + dirent_first(dirp); +} + +/* Get first directory entry */ +static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp) +{ + if (!dirp) + return NULL; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle == INVALID_HANDLE_VALUE) + goto error; + + /* A directory entry is now waiting in memory */ + dirp->cached = 1; + return &dirp->data; + +error: + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + + /* Set error code */ + DWORD errorcode = GetLastError(); + switch (errorcode) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno(EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno(ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno(ENOENT); + } + return NULL; +} + +/* Get next directory entry */ +static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp) +{ + /* Is the next directory entry already in cache? */ + if (dirp->cached) { + /* Yes, a valid directory entry found in memory */ + dirp->cached = 0; + return &dirp->data; + } + + /* No directory entry in cache */ + if (dirp->handle == INVALID_HANDLE_VALUE) + return NULL; + + /* Read the next directory entry from stream */ + if (FindNextFileW(dirp->handle, &dirp->data) == FALSE) + goto exit_close; + + /* Success */ + return &dirp->data; + + /* Failure */ +exit_close: + FindClose(dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + return NULL; +} + +/* Open directory stream using plain old C-string */ +static DIR *opendir(const char *dirname) +{ + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + struct DIR *dirp = (DIR*) malloc(sizeof(struct DIR)); + if (!dirp) + return NULL; + + /* Convert directory name to wide-character string */ + wchar_t wname[PATH_MAX + 1]; + size_t n; + int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX+1); + if (error) + goto exit_failure; + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir(wname); + if (!dirp->wdirp) + goto exit_failure; + + /* Success */ + return dirp; + + /* Failure */ +exit_failure: + free(dirp); + return NULL; +} + +/* Read next directory entry */ +static struct dirent *readdir(DIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct dirent *entry; + (void) readdir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result) +{ + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp->wdirp); + if (!datap) { + /* No more directory entries */ + *result = NULL; + return /*OK*/0; + } + + /* Attempt to convert file name to multi-byte string */ + size_t n; + int error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, then + * attempt to use old 8+3 file name. This allows the program to + * access files although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file name + * unless the file system provides one. At least VirtualBox shared + * folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof(struct dirent); + } else { + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + } + + /* Return pointer to directory entry */ + *result = entry; + return /*OK*/0; +} + +/* Close directory stream */ +static int closedir(DIR *dirp) +{ + int ok; + + if (!dirp) + goto exit_failure; + + /* Close wide-character directory stream */ + ok = _wclosedir(dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free(dirp); + return ok; + +exit_failure: + /* Invalid directory stream */ + dirent_set_errno(EBADF); + return /*failure*/-1; +} + +/* Rewind directory stream to beginning */ +static void rewinddir(DIR* dirp) +{ + if (!dirp) + return; + + /* Rewind wide-character string directory stream */ + _wrewinddir(dirp->wdirp); +} + +/* Scan directory for entries */ +static int scandir( + const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + int result; + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Cannot open directory */ + return /*Error*/ -1; + } + + /* Read directory entries to memory */ + struct dirent *tmp = NULL; + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + while (1) { + /* Allocate room for a temporary directory entry */ + if (!tmp) { + tmp = (struct dirent*) malloc(sizeof(struct dirent)); + if (!tmp) + goto exit_failure; + } + + /* Read directory entry to temporary area */ + struct dirent *entry; + if (readdir_r(dir, tmp, &entry) != /*OK*/0) + goto exit_failure; + + /* Stop if we already read the last directory entry */ + if (entry == NULL) + goto exit_success; + + /* Determine whether to include the entry in results */ + if (filter && !filter(tmp)) + continue; + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + /* Compute number of entries in the new table */ + size_t num_entries = size * 2 + 16; + + /* Allocate new pointer table or enlarge existing */ + void *p = realloc(files, sizeof(void*) * num_entries); + if (!p) + goto exit_failure; + + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } + + /* Store the temporary entry to ptr table */ + files[size++] = tmp; + tmp = NULL; + } + +exit_failure: + /* Release allocated file entries */ + for (size_t i = 0; i < size; i++) { + free(files[i]); + } + + /* Release the pointer table */ + free(files); + files = NULL; + + /* Exit with error code */ + result = /*error*/ -1; + goto exit_status; + +exit_success: + /* Sort directory entries */ + qsort(files, size, sizeof(void*), + (int (*) (const void*, const void*)) compare); + + /* Pass pointer table to caller */ + if (namelist) + *namelist = files; + + /* Return the number of directory entries read */ + result = (int) size; + +exit_status: + /* Release temporary directory entry, if we had one */ + free(tmp); + + /* Close directory stream */ + closedir(dir); + return result; +} + +/* Alphabetical sorting */ +static int alphasort(const struct dirent **a, const struct dirent **b) +{ + return strcoll((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int versionsort(const struct dirent **a, const struct dirent **b) +{ + return strverscmp((*a)->d_name, (*b)->d_name); +} + +/* Compare strings */ +static int strverscmp(const char *a, const char *b) +{ + size_t i = 0; + size_t j; + + /* Find first difference */ + while (a[i] == b[i]) { + if (a[i] == '\0') { + /* No difference */ + return 0; + } + ++i; + } + + /* Count backwards and find the leftmost digit */ + j = i; + while (j > 0 && isdigit((unsigned char)a[j-1])) { + --j; + } + + /* Determine mode of comparison */ + if (a[j] == '0' || b[j] == '0') { + /* Find the next non-zero digit */ + while (a[j] == '0' && a[j] == b[j]) { + j++; + } + + /* String with more digits is smaller, e.g 002 < 01 */ + if (isdigit((unsigned char)a[j])) { + if (!isdigit((unsigned char)b[j])) { + return -1; + } + } else if ((unsigned char)isdigit(b[j])) { + return 1; + } + } else if ((unsigned char)isdigit(a[j]) && + isdigit((unsigned char)b[j])) { + /* Numeric comparison */ + size_t k1 = j; + size_t k2 = j; + + /* Compute number of digits in each string */ + while (isdigit((unsigned char)a[k1])) { + k1++; + } + while (isdigit((unsigned char)b[k2])) { + k2++; + } + + /* Number with more digits is bigger, e.g 999 < 1000 */ + if (k1 < k2) + return -1; + else if (k1 > k2) + return 1; + } + + /* Alphabetical comparison */ + return (int) ((unsigned char) a[i]) - ((unsigned char) b[i]); +} + +/* Convert multi-byte string to wide character string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, + size_t sizeInWords, const char *mbstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = mbstowcs(wcstr, mbstr, sizeInWords); + if (wcstr && n >= count) + return /*error*/ 1; + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) + n = sizeInWords - 1; + wcstr[n] = 0; + } + + /* Length of multi-byte string with zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Convert wide-character string to multi-byte string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, + size_t sizeInBytes, const wchar_t *wcstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = wcstombs(mbstr, wcstr, sizeInBytes); + if (mbstr && n >= count) + return /*error*/1; + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Set errno variable */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void dirent_set_errno(int error) +{ + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; +} +#endif + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 00000000000..bd753b34d7d --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/man/bomtool.1 b/man/bomtool.1 new file mode 100644 index 00000000000..aac6c8014ea --- /dev/null +++ b/man/bomtool.1 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2025 pkgconf authors (see AUTHORS). +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" This software is provided 'as is' and without any warranty, express or +.\" implied. In no event shall the authors be liable for any damages arising +.\" from the use of this software. +.Dd June 4, 2025 +.Dt BOMTOOL 1 +.Os +.Sh NAME +.Nm bomtool +.Nd a tool for generating SPDX-based software bills of material +.Sh SYNOPSIS +.Nm +.Op Ar options +.Ar module ... +.Sh DESCRIPTION +.Nm +is a program which generates a textual SPDX 2.0 software bill of +materials (SBOM) for a given set of pkg-config modules. +The output of this tool can then be translated into other SBOM +formats as necessary. +.Pp +The +.Ar options +are as follows: +.Bl -tag -width indent +.It Fl -about +Print the version number, the Copyright notice, and the license of the +.Nm +program to standard output and exit. +Most other options and all command line arguments are ignored. +.It Fl -version +Print the version number of the +.Nm +program to standard output and exit. +Most other options and all command line arguments are ignored. +.El +.Sh ENVIRONMENT +.Bl -tag -width indent +.It Ev PKG_CONFIG_DEBUG_SPEW +If set, print debugging messages to stderr. +.It Ev PKG_CONFIG_IGNORE_CONFLICTS +If set, ignore +.Ic Conflicts +rules in modules. +Has the same effect as the +.Fl -ignore-conflicts +option in +.Xr pkgconf 1 +. +.It Ev PKG_CONFIG_LIBDIR +A colon-separated list of low-priority directories where +.Xr pc 5 +files are looked up. +The module search path is constructed by appending this list to +.Ev PKG_CONFIG_PATH , +which enjoys higher priority. +If +.Ev PKG_CONFIG_LIBDIR +is not defined, the default list compiled into the +.Nm +program from the +.Dv PKG_DEFAULT_PATH +preprocessor macro is appended instead. +If +.Ev PKG_CONFIG_LIBDIR +is defined but empty, nothing is appended. +.It Ev PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH +Impose a limit on the allowed depth in the dependency graph. +.It Ev PKG_CONFIG_PATH +A colon-separated list of high-priority directories where +.Xr pc 5 +files are looked up. +.It Ev PKG_CONFIG_PRELOADED_FILES +Colon-separated list of +.Xr pc 5 +files which are loaded before any other pkg-config files. +These packages are given highest priority over any other +.Xr pc 5 +files that would otherwise provide a given package. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Generating an SBOM for the package named foo: +.Dl $ bomtool foo +.Dl SPDXVersion: SPDX-2.2 +.Dl DataLicense: CC0-1.0 +.Dl SPDXID: SPDXRef-DOCUMENT +.Dl DocumentName: SBOM-SPDX-fooC641.2.3 +.Dl DocumentNamespace: https://spdx.org/spdxdocs/bomtool-2.4.3 +.Dl Creator: Tool: bomtool 2.4.3 +.Dl [...] +.Sh SEE ALSO +.Xr pc 5 , +.Xr pkgconf 1 diff --git a/man/pc.5 b/man/pc.5 new file mode 100644 index 00000000000..2aee7462b07 --- /dev/null +++ b/man/pc.5 @@ -0,0 +1,178 @@ +.\" Copyright (c) 2017 pkgconf authors (see AUTHORS). +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" This software is provided 'as is' and without any warranty, express or +.\" implied. In no event shall the authors be liable for any damages arising +.\" from the use of this software. +.Dd December 15, 2017 +.Dt PC 5 +.Os +.Sh NAME +.Nm file.pc +.Nd pkg-config file format +.Sh DESCRIPTION +pkg-config files provide a useful mechanism for storing various information +about libraries and packages on a given system. +Information stored by +.Nm .pc +files include compiler and linker flags necessary to use a given library, as +well as any other relevant metadata. +.Pp +These +.Nm .pc +files are processed by a utility called +.Nm pkg-config , +of which +.Nm pkgconf +is an implementation. +.\" +.Ss FILE SYNTAX +The +.Nm .pc +file follows a format inspired by RFC822. +Comments are prefixed by a pound sign, hash sign or octothorpe (#), and variable +assignment is similar to POSIX shell. +Properties are defined using RFC822-style stanzas. +.\" +.Ss VARIABLES +.\" +Variable definitions start with an alphanumeric string, followed by an equal sign, +and then the value the variable should contain. +.Pp +Variable references are always written as "${variable}". +It is possible to escape literal "${" as "$${". +.\" +.Ss PROPERTIES +.\" +Properties are set using RFC822-style stanzas which consist of a keyword, followed +by a colon (:) and then the value the property should be set to. +Variable substitution is always performed regardless of property type. +.Pp +There are three types of property: +.\" +.Bl -tag -width indent +.\" +.It Literal +The property will be set to the text of the value. +.\" +.It Dependency List +The property will be set to a list of dependencies parsed from the +text. +Dependency lists are defined by this ABNF syntax: +.Bd -literal +package-list = *WSP *( package-spec *( package-sep ) ) +package-sep = WSP / "," +.\" +package-spec = package-key [ ver-op package-version ] +ver-op = "<" / "<=" / "=" / "!=" / ">=" / ">" +.Ed +.\" +.It Fragment List +The property will be set to a list of fragments parsed from the text. +The input text must be in a format that is suitable for passing to a POSIX +shell without any shell expansions after variable substitution has been done. +.\" +.El +.Ss PROPERTY KEYWORDS +.Bl -tag -width indent +.\" +.It Name +The displayed name of the package. +(mandatory; literal) +.It Version +The version of the package. +(mandatory; literal) +.It Description +A description of the package. +(mandatory; literal) +.It URL +A URL to a webpage for the package. +This is used to recommend where newer versions of the package can be acquired. +(mandatory; literal) +.It Cflags +Required compiler flags. +These flags are always used, regardless of whether static compilation is requested. +(optional; fragment list) +.It Cflags.private +Required compiler flags for static compilation. +(optional; fragment list; pkgconf extension) +.It Copyright +A copyright attestation statement. +(optional; literal; pkgconf extension) +.It Libs +Required linking flags for this package. +Libraries this package depends on for linking against it, which are not +described as dependencies should be specified here. +(optional; fragment list) +.It Libs.private +Required linking flags for this package that are only required when linking +statically. +Libraries this package depends on for linking against it statically, which are +not described as dependencies should be specified here. +(optional; fragment list) +.It License +The asserted SPDX license tag that should be applied to the given package. +(optional; literal; pkgconf extension) +.It Maintainer +The preferred contact for the maintainer. This should be in the format of a +name followed by an e-mail address or website. +(optional; literal; pkgconf extension) +.It Requires +Required dependencies that must be met for the package to be usable. +All dependencies must be satisfied or the pkg-config implementation must not use +the package. +(optional; dependency list) +.It Requires.private +Required dependencies that must be met for the package to be usable for header +inclusion and static linking. +All dependencies must be satisfied or the pkg-config implementation must not use +the package for header inclusion and static linking. +(optional; dependency list) +.It Conflicts +Dependencies that must not be met for the package to be usable. +If any package in the proposed dependency solution match any dependency in the +Conflicts list, the package being considered is not usable. +(optional; dependency list) +.It Provides +Dependencies that may be provided by an alternate package. +If a package cannot be found, the entire package collection is scanned for +providers which can match the requested dependency. +(optional; dependency list; pkgconf extension) +.El +.Ss EXTENSIONS +Features that have been marked as a pkgconf extension are only guaranteed to work +with the pkgconf implementation of pkg-config. +Other implementations may or may not support the extensions. +.Pp +Accordingly, it is suggested that +.Nm .pc +files which absolutely depend on these extensions declare a requirement on the +pkgconf virtual. +.Sh EXAMPLES +An example .pc file: +.Bd -literal +# This is a comment +prefix=/home/kaniini/pkg # this defines a variable +exec_prefix=${prefix} # defining another variable with a substitution +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: libfoo # human-readable name +Description: an example library called libfoo # human-readable description +Copyright: Copyright (c) 2022 pkgconf project authors +License: Apache-2.0 +Maintainer: the pkgconf project +Version: 1.0 +URL: http://www.pkgconf.org +Requires: libbar > 2.0.0 +Conflicts: libbaz <= 3.0.0 +Libs: -L${libdir} -lfoo +Libs.private: -lm +Cflags: -I${includedir}/libfoo +.Ed +.Sh SEE ALSO +.Xr pkgconf 1 , +.Xr pkg.m4 7 diff --git a/man/pkg.m4.7 b/man/pkg.m4.7 new file mode 100644 index 00000000000..f9210700f8f --- /dev/null +++ b/man/pkg.m4.7 @@ -0,0 +1,143 @@ +.\" Copyright (c) 2017 pkgconf authors (see AUTHORS). +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" This software is provided 'as is' and without any warranty, express or +.\" implied. In no event shall the authors be liable for any damages arising +.\" from the use of this software. +.Dd December 5, 2017 +.Dt PKG.M4 7 +.Os +.Sh NAME +.Nm pkg.m4 +.Nd autoconf macros for using pkgconf +.Sh SYNOPSIS +.Nm PKG_PREREQ +.Nm PKG_PROG_PKG_CONFIG +.Nm PKG_CHECK_MODULES +.Nm PKG_CHECK_MODULES_STATIC +.Nm PKG_INSTALLDIR +.Nm PKG_NOARCH_INSTALLDIR +.Nm PKG_CHECK_VAR +.Nm PKG_WITH_MODULES +.Nm PKG_HAVE_WITH_MODULES +.Nm PKG_HAVE_DEFINE_WITH_MODULES +.Sh DESCRIPTION +.Nm +is a collection of autoconf macros which help to configure compiler and linker +flags for development libraries. +This allows build systems to detect other dependencies and use them with the +system toolchain. +.Sh "AUTOCONF MACROS" +.Ss "PKG_PREREQ(MIN-VERSION)" +Checks that the version of the +.Nm +autoconf macros in use is at least MIN-VERSION. +This can be used to ensure a particular +.Nm +macro will be available. +.Ss "PKG_PROG_PKG_CONFIG([MIN-VERSION])" +Checks for an implementation of +.Nm pkg-config +which is at least MIN-VERSION or newer. +.Ss "PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]])" +.Ss "PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]])" +Checks whether a given module set exists, and if so, defines +.Nm CFLAGS +and +.Nm LIBS +variables prefixed by +.Nm VARIABLE-PREFIX +with the output from +.Fl -cflags +and +.Fl -libs +respectively. +.Pp +The optional +.Nm ACTION-IF-FOUND +and +.Nm ACTION-IF-NOT-FOUND +arguments are shell fragments that should be executed if the module set is +found or not found. +.Pp +If +.Nm $PKG_CONFIG +is not defined, the +.Nm PKG_PROG_PKG_CONFIG +macro will be executed to locate a +.Nm pkg-config +implementation. +.Pp +The +.Nm PKG_CHECK_MODULES_STATIC +macro provides the same behaviour as +.Nm PKG_CHECK_MODULES +with static linking enabled via the +.Fl -static +flag. +.Ss "PKG_INSTALLDIR(DIRECTORY)" +Defines the variable $pkgconfigdir as the location where a package +should install pkg-config .pc files. +.Pp +By default the directory is $libdir/pkgconfig, but the default can +be changed by passing the +.Nm DIRECTORY +parameter. +.Pp +This value can be overridden with the +.Fl -with-pkgconfigdir +configure parameter. +.Ss "PKG_NOARCH_INSTALLDIR(DIRECTORY)" +Defines the variable $noarch_pkgconfigdir as the location where a package +should install pkg-config .pc files. +.Pp +By default the directory is $datadir/pkgconfig, but the default can +be changed by passing the +.Nm DIRECTORY +parameter. +.Pp +This value can be overridden with the +.Fl -with-noarch-pkgconfigdir +configure parameter. +.Ss "PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])" +Retrieves the value of the +.Nm pkg-config +variable +.Nm CONFIG-VARIABLE +from +.Nm MODULE +and stores it in the +.Nm VARIABLE +variable. +.Pp +Note that repeated usage of +.Nm VARIABLE +is not recommended as the check will be skipped if the variable is +already set. +.Ss "PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], [DESCRIPTION], [DEFAULT])" +Prepares a "--with-" configure option using the lowercase +.Nm VARIABLE-PREFIX +name, merging the behaviour of +.Nm AC_ARG_WITH +and +.Nm PKG_CHECK_MODULES +in a single macro. +.Ss "PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, [DESCRIPTION], [DEFAULT])" +Convenience macro to trigger +.Nm AM_CONDITIONAL +after a +.Nm PKG_WITH_MODULES check.\& +.Nm VARIABLE-PREFIX +is exported as a make variable. +.Ss "PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, [DESCRIPTION], [DEFAULT])" +Convenience macro to trigger +.Nm AM_CONDITIONAL +and +.Nm AC_DEFINE +after a +.Nm PKG_WITH_MODULES check.\& +.Nm VARIABLE-PREFIX +is exported as a make variable. diff --git a/man/pkgconf-personality.5 b/man/pkgconf-personality.5 new file mode 100644 index 00000000000..eee70b41cf0 --- /dev/null +++ b/man/pkgconf-personality.5 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2018 pkgconf authors (see AUTHORS). +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" This software is provided 'as is' and without any warranty, express or +.\" implied. In no event shall the authors be liable for any damages arising +.\" from the use of this software. +.Dd July 19, 2018 +.Dt PKGCONF-PERSONALITY 5 +.Os +.Sh NAME +.Nm file.personality +.Nd pkgconf cross-compile personality file format +.Sh DESCRIPTION +pkgconf cross-compile personality files provide a useful mechanism for storing +various information about system toolchains. +Information stored by +.Nm .personality +files include information about paths used by a cross-compile toolchain, such as +the sysroot directory and default include and library paths. pkgconf uses this +information to determine what information is necessary to use libraries. +.\" +.Ss FILE SYNTAX +The +.Nm .personality +file follows a format inspired by RFC822. +Comments are prefixed by a pound sign, hash sign or octothorpe (#), and variable +assignment is similar to POSIX shell. +Properties are defined using RFC822-style stanzas. +.\" +.Ss PROPERTIES +.\" +Properties are set using RFC822-style stanzas which consist of a keyword, followed +by a colon (:) and then the value the property should be set to. +Variable substitution is always performed regardless of property type. +.Pp +There are three types of property: +.\" +.Bl -tag -width indent +.\" +.It Literal +The property will be set to the text of the value. +.\" +.It Fragment List +The property will be set to a list of fragments parsed from the text. +The input text must be in a format that is suitable for passing to a POSIX +shell without any shell expansions after variable substitution has been done. +Elements are delimited with a colon. +.\" +.It Boolean +The property will be set to true if the value is one of: true, yes or 1. +Otherwise it will be set to false. +.\" +.El +.Ss PROPERTY KEYWORDS +.Bl -tag -width indent +.\" +.It Triplet +The triplet used by the cross-compile toolchain. +(mandatory; literal) +.It SysrootDir +The directory used by the system root of the cross-compile toolchain. +(mandatory; literal) +.It DefaultSearchPaths +A list of directories to look for +.Xr pc 5 +files in. +(mandatory; fragment list) +.It SystemIncludePaths +A list of directories that are included by default in the search path for +include files. +(mandatory; fragment list) +.It SystemLibraryPaths +A list of directories that are included by default in the search path for +libraries. +(mandatory; fragment list) +.It WantDefaultPure +If true, pkgconf will default to preferring a pure dependency graph. +(optional; boolean; default is false) +.It WantDefaultStatic +If true, pkgconf will default to operating in static linking mode. +(optional; boolean; default is false) +.\" +.El +.Sh EXAMPLES +An example .personality file: +.Bd -literal +# This is a comment +Triplet: x86_64-pc-linux-gnu +SysrootDir: /home/kaniini/sysroot/x86_64-pc-linux-gnu +DefaultSearchPaths: /home/kaniini/sysroot/x86_64-pc-linux-gnu/lib/pkgconfig:/home/kaniini/sysroot/x86_64-pc-linux-gnu/share/pkgconfig +SystemIncludePaths: /home/kaniini/sysroot/x86_64-pc-linux-gnu/include +SystemLibraryPaths: /home/kaniini/sysroot/x86_64-pc-linux-gnu/lib +.Ed +.Sh SEE ALSO +.Xr pkgconf 1 , +.Xr pc 5 , +.Xr pkg.m4 7 diff --git a/man/pkgconf.1 b/man/pkgconf.1 new file mode 100644 index 00000000000..df286a60593 --- /dev/null +++ b/man/pkgconf.1 @@ -0,0 +1,758 @@ +.\" Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 pkgconf authors (see AUTHORS). +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" This software is provided 'as is' and without any warranty, express or +.\" implied. In no event shall the authors be liable for any damages arising +.\" from the use of this software. +.Dd November 15, 2016 +.Dt PKGCONF 1 +.Os +.Sh NAME +.Nm pkgconf +.Nd a system for configuring build dependency information +.Sh SYNOPSIS +.Nm +.Op Ar options +.Ar module ... +.Sh DESCRIPTION +The +.Nm +program retrieves configuration information related to the +.Ar module +arguments from +.Xr pc 5 +files installed on the system and prints parts of the retrieved +information depending on the specified +.Ar options . +The most common use is printing the compiler and linker flags needed +to build software that uses the libraries given by the +.Ar module +arguments. +.Pp +The +.Xr pc 5 +files are searched for along a path constructed from the +.Fl -with-path +option, the +.Ev PKG_CONFIG_PATH +and +.Ev PKG_CONFIG_LIBDIR +environment variables, and some compiled-in default directories. +The +.Ar module +arguments correspond to the file names, but without the +.Pa .pc +filename extension. +.Pp +Several of the +.Ar options +cause immediate exit. +If multiple of these options are given, only the option with the +highest priority takes effect and those with lower priority are +silently ignored. +These options are, ordered by descending priority: +.Bl -enum +.It +No-module options: +.Fl -relocate , +.Fl -dump-personality , +.Fl -about , +.Fl -version , +.Fl -help , +.Fl -atleast-pkgconfig-version , +.Fl -list-all , +and +.Fl -list-package-names : +These options cause all arguments to be ignored. +.It +Argument-only options: +.Fl -atleast-version , +.Fl -exact-version , +and +.Fl -max-version : +These options only inspect modules explicitly specified on the +command line and do not look at dependencies. +.It +Limited-output options: +.Fl -validate , +.Fl -license , +.Fl -uninstalled , +and +.Fl -env : +These options perform dependency resolution, but exit after printing +the information requested by the highest-priority option, +ignoring other output options that may have been specified. +.El +.Pp +Several other options require at least one +.Ar module +argument, produce output, do not cause early exit, can be combined +with each other, but override and disable all +.Fl -cflags +and +.Fl -libs +options: +.Bl -enum +.It +Single-module output options: +.Fl -path , +.Fl -print-variables , +.Fl -variable : +If any of these options is specified, only the first +.Ar module +argument is used, all other arguments are silently ignored, +and no dependency resolution is attempted. +.It +Depth-one output options: +.Fl -print-provides , +.Fl -modversion , +.Fl -print-requires , +and +.Fl -print-requires-private : +If any of these options is specified, only modules +explicitly specified on the command line are inspected +and no dependency resolution is attempted. +.It +General output options: +.Fl -simulate , +.Fl -digraph , +.Fl -solution , +.Fl -fragment-tree : +These options do not limit dependency resolution. +.El +.Pp +The most important output options +.Fl -cflags +and +.Fl -libs +can be combined with each other, but are overridden and ignored if +any of the options listed above are specified. +.Pp +The complete list of +.Ar options +is as follows: +.Bl -tag -width indent +.It Fl -about +Print the version number, the Copyright notice, and the license of the +.Nm +program to standard output and exit. +Most other options and all command line arguments are ignored. +.It Fl -atleast-pkgconfig-version Ns = Ns Ar version +Exit with error if the requested +.Ar version +number is greater than the version number of the +.Nm +program, or with success otherwise. +Most other options and all command line arguments are ignored. +.It Fl -atleast-version Ns = Ns Ar version +Check the +.Ar module +arguments in the given order. +Exit with error as soon as a +.Ar module +does not exist, and exit with success as soon as the version number of a +.Ar module +is greater than or equal to the requested +.Ar version +number. +Exit with error if the version number of each +.Ar module +is less than the requested +.Ar version +number. +.It Fl -cflags , Fl -cflags-only-I , Fl -cflags-only-other +Print all compiler flags required to compile against the +.Ar module , +or only the include path +.Pq Fl I +flags, or only the compiler flags that are not include path flags, +respectively. +These options imply +.Fl -print-errors . +.It Fl -debug +Print some non-fatal warning messages to standard error output +that would otherwise silently be ignored. +This option also implies +.Fl -print-errors . +If +.Nm +was compiled without defining the preprocessor macro +.Dv PKGCONF_LITE , +this option also prints many debugging messages to standard error output. +.It Fl -define-prefix +Attempts to determine the prefix variable to use for CFLAGS and LIBS entry relocations. +This is mainly useful for platforms where framework SDKs are relocatable, such as Windows. +.It Fl -define-variable Ns = Ns Ar varname Ns = Ns Ar value +Define +.Ar varname +as +.Ar value . +Variables are used in query output, and some modules' results may change based +on the presence of a variable definition. +.It Fl -digraph +Dump the dependency resolver's solution as a graphviz +.Sq dot +file. +This can be used with graphviz to visualize module interdependencies. +This option is only available if the preprocessor macro +.Dv PKGCONF_LITE +was not defined during compilation. +.It Fl -dont-define-prefix +Disables the +.Sq define-prefix +feature. +.It Fl -dont-relocate-paths +Disables the path relocation feature. +.It Fl -dump-personality +Print some default settings to standard output, in particular +the default module search path that is used when +.Ev PKG_CONFIG_LIBDIR +is not defined, the default list of include paths that are filtered out when +.Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH +is not defined, +and the default list of library paths that are filtered out when +.Ev PKG_CONFIG_SYSTEM_LIBRARY_PATH +is not defined, and exit. +Most other options and all command line arguments are ignored. +This option is only available if the preprocessor macro +.Dv PKGCONF_LITE +was not defined during compilation. +.It Fl -env Ns = Ns Ar varname +Print the requested values as variable declarations in a similar format as the +.Xr env 1 +command. +.It Fl -env-only +Initialize the module search path from +.Fl -with-path +and +.Ev PKG_CONFIG_PATH +only, ignoring +.Ev PKG_CONFIG_LIBDIR +and the compiled-in default directories. +.It Fl -errors-to-stdout +Print all error, warning, and debugging messages to standard output +instead of to standard error output. +.It Fl -exact-version Ns = Ns Ar version +Check the +.Ar module +arguments in the given order. +Exit with error as soon as a +.Ar module +does not exist, and exit with success as soon as the version number of a +.Ar module +is exactly the requested +.Ar version +number. +Exit with error if the version number of each +.Ar module +differs from the requested +.Ar version +number. +.It Fl -exists +Exit with a non-zero exit status +if the dependency resolver is unable to find all of the requested +.Ar module Ns s . +This option is active by default and cannot be disabled. +However, various other options cause +.Nm +to exit and report success or failure before all arguments have been inspected. +.It Fl -fragment-filter Ns = Ns Ar types +Filter the fragment lists for the specified +.Ar types . +.It Fl -help +Print a usage summary on standard output and exit. +Most other options and all command line arguments are ignored. +.It Fl -ignore-conflicts +Ignore +.Sq Conflicts +rules in modules. +.It Fl -keep-system-cflags , Fl -keep-system-libs +Keep CFLAGS or linker flag fragments that would be filtered due to being +included by default in the compiler. +.It Fl -libs , Fl -libs-only-L , Fl -libs-only-l , Fl -libs-only-other +Print all linker flags required to link against the +.Ar module , +or only the library path +.Pq Fl L +flags, or only the library +.Pq Fl l +flags, or only the linker flags that are neither library path +nor library flags, respectively. +These options imply +.Fl -print-errors . +.It Fl -list-all +Walk the module search path in the order of descending priority. +For each +.Xr pc 5 +file found, print one line to standard output, +containing the basename of the file without the extension, the +.Ic Name +property, a dash +.Pq Sq \- , +and the +.Ic Description +property. +This option implies +.Fl -print-errors . +All command line arguments are ignored. +.It Fl -list-package-names +Perform the same search as +.Fl -list-all , +but only print the basename of each +.Xr pc 5 +file without the extension, not the module name and the description. +This option implies +.Fl -print-errors . +All command line arguments are ignored. +.It Fl -log-file Ns = Ns Ar file +Set the name of the output +.Ar file +where information about selected modules is logged, +both about those selected by arguments and as dependencies. +For each selected module, one line is printed, +containing the basename of the +.Xr pc 5 +file without the extension, optionally an operator and version number +describing the desired range of versions, and either the actual version +number in square brackets or the string +.Qq NOT-FOUND . +If this option is not provided, the name of the output file +is instead taken from the +.Ev PKG_CONFIG_LOG +environment variable, and if that is not provided either, +this kind of logging is disabled. +.It Fl -max-version Ns = Ns Ar version +Check the +.Ar module +arguments in the given order. +Exit with error as soon as a +.Ar module +does not exist, and exit with success as soon as the version number of a +.Ar module +is less than or equal to the requested +.Ar version +number. +Exit with error if the version number of each +.Ar module +is greater than the requested +.Ar version +number. +.It Fl -maximum-traverse-depth Ns = Ns Ar depth +Impose a limit on the allowed depth in the dependency graph. +For example, a +.Ar depth +of 2 restricts the resolver from acting on child +dependencies of modules added to the resolver's solution. +This option is overridden by the +.Ev PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH +environment variable and by the options +.Fl -modversion , +.Fl -path , +.Fl -print-provides , +.Fl -print-requires , +.Fl -print-requires-private , +.Fl -print-variables , +and +.Fl -variable . +.It Fl -modversion +For each specified +.Ar module , +print the version number to standard output. +If the +.Fl -verbose +option is also specified, the name of the respective +.Ar module +and a colon is printed before each version number. +This option implies +.Fl -print-errors +and +.Fl -maximum-traverse-depth Ns =1 +and overrides and disables all +.Fl -cflags +and +.Fl -libs +flags. +.It Fl -msvc-syntax +Use MSVC syntax for +.Fl -cflags , +.Fl -env , +and +.Fl -libs +output. +This option is only available if the preprocessor macro +.Dv PKGCONF_LITE +was not defined during compilation. +.It Fl -no-cache +Skip caching packages when they are loaded into the internal resolver. +This may result in an alternate dependency graph being computed. +.It Fl -no-provides +Ignore +.Sq Provides +rules in modules when resolving dependencies. +.It Fl -no-uninstalled +Forbids the dependency resolver from considering 'uninstalled' modules as part +of a solution. +.It Fl -path +For the first +.Ar module +given on the command line, let the dependency resolver find the +.Xr pc 5 +file describing that module, print the absolute pathname of that file +to standard output, and exit immediately, +ignoring most other options and all other arguments. +.It Fl -prefix-variable Ns = Ns Ar variable +Sets the +.Sq prefix +variable used by the +.Sq define-prefix +feature. +.It Fl -print-errors +Print some messages about fatal errors to standard error output +that would otherwise be omitted. +This option is implied by many other options, but not by all. +It can be overridden with +.Fl -silence-errors . +.It Fl -print-provides +For each specified +.Ar module , +print one line to standard output containing the +.Ic Name +property, an equal sign +.Pq Sq = , +and the +.Ic Version +property. +If the +.Ar module +contains one or more +.Ic Provides +properties, print additional lines in dependency list format, one name +per line, each name optionally followed by an operator and a version. +This option implies +.Fl -maximum-traverse-depth Ns =1 +and overrides and disables all +.Fl -cflags +and +.Fl -libs +flags. +.It Fl -print-requires , Fl -print-requires-private +For each specified +.Ar module , +print the +.Ic Requires +or +.Ic Requires.private +properties, respectively, in dependency list format to standard output. +Both of these options imply +.Fl -maximum-traverse-depth Ns =1 +and override and disable all +.Fl -cflags +and +.Fl -libs +flags. +.It Fl -print-variables +For the first +.Ar module +given on the command line, print the names of all seen variables +to standard output, one per line. +Any subsequent arguments are silently ignored. +This option implies +.Fl -print-errors +and +.Fl -maximum-traverse-depth Ns =1 +and overrides and disables all +.Fl -cflags +and +.Fl -libs +flags. +.It Fl -pure +Treats the computed dependency graph as if it were pure. +This is mainly intended for use with the +.Fl -static +flag and has no effect if +.Fl -shared +is also specified. +.It Fl -relocate Ns = Ns Ar path +Relocates a path using the pkgconf_path_relocate API. +This is mainly used by the testsuite to provide a guaranteed interface +to the system's path relocation backend. +.It Fl -shared +Compute a simple dependency graph that is only suitable for shared linking. +This option overrides +.Fl -static . +.It Fl -short-errors +When printing error messages about modules that are not found +or conflict with each other, avoid printing additional, verbose +instructions explaining potential methods for solving the problem. +.It Fl -silence-errors +Do not print any error, warning, or debugging messages at all. +Overrides all of +.Fl -debug , +.Fl -errors-to-stdout , +and +.Fl -print-errors . +This option is overridden and disabled if the +.Ev PKG_CONFIG_DEBUG_SPEW +environment variable is set. +.It Fl -simulate +Simulates resolving a dependency graph based on the requested modules on the +command line. +Dumps a series of trees denoting pkgconf's resolver state. +This option is only available if the preprocessor macro +.Dv PKGCONF_LITE +was not defined during compilation. +.It Fl -solution +Print the names of the modules requested with +.Ar module +arguments and their dependencies to standard output. +This option is only available if the preprocessor macro +.Dv PKGCONF_LITE +was not defined during compilation. +.It Fl -static +Compute a deeper dependency graph and use compiler/linker flags intended for +static linking. +This option is overridden by +.Fl -shared . +.It Fl -uninstalled +Exit with a non-zero result if the dependency resolver uses an +.Sq uninstalled +module as part of its solution. +.It Fl -validate Ar package ... +Validate specific +.Sq .pc +files for correctness. +This option implies +.Fl -print-errors +and +.Fl -errors-to-stdout . +.It Fl -variable Ns = Ns Ar varname +For the first +.Ar module +given on the command line, print the value of the variable with the name +.Ar varname +to standard output. +Any subsequent arguments are silently ignored. +This option implies +.Fl -maximum-traverse-depth Ns =1 +and overrides and disables all +.Fl -cflags +and +.Fl -libs +flags. +.It Fl -verbose +This option only has an effect if +.Fl -modversion +is also specified. +It prints the name of the respective +.Ar module +and a colon before each version number. +.It Fl -version +Print the version number of the +.Nm +program to standard output and exit. +Most other options and all command line arguments are ignored. +.It Fl -with-path Ns = Ns Ar path +Prepend the directory +.Ar path +to the module search path, +giving it priority over all other directories including those from +.Ev PKG_CONFIG_PATH +and +.Ev PKG_CONFIG_LIBDIR . +.El +.Sh ENVIRONMENT +.Bl -tag -width indent +.It Ev CPATH +First supplementary colon-separated list of include paths filtered out +in the same way as +.Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH . +.It Ev CPLUS_INCLUDE_PATH +Third supplementary colon-separated list of include paths filtered out +in the same way as +.Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH . +.It Ev C_INCLUDE_PATH +Second supplementary colon-separated list of include paths filtered out +in the same way as +.Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH . +.It Ev DESTDIR +If set to the same value as +.Ev PKG_CONFIG_SYSROOT_DIR , +behave in the same way as if +.Ev PKG_CONFIG_FDO_SYSROOT_RULES +is set. +If +.Ev PKG_CONFIG_SYSROOT_DIR +is not set or set to a different value, +.Ev DESTDIR +is ignored. +.It Ev LIBRARY_PATH +Supplementary colon-separated list of library paths filtered out +in the same way as +.Ev PKG_CONFIG_SYSTEM_LIBRARY_PATH . +.It Ev OBJC_INCLUDE_PATH +Fourth supplementary colon-separated list of include paths filtered out +in the same way as +.Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH . +.It Ev PKG_CONFIG_ALLOW_SYSTEM_CFLAGS +If set, this variable has the same effect as the +.Fl -keep-system-cflags +option. +.It Ev PKG_CONFIG_ALLOW_SYSTEM_LIBS +If set, this variable has the same effect as the +.Fl -keep-system-libs +option. +.It Ev PKG_CONFIG_DEBUG_SPEW +If set, override and disable the +.Fl -silence-errors +option. +.It Ev PKG_CONFIG_DISABLE_UNINSTALLED +If set, enables the same behaviour as the +.Fl -no-uninstalled +flag. +.It Ev PKG_CONFIG_DONT_DEFINE_PREFIX +If set, this variable has the same effect as the +.Fl -dont-define-prefix +option. +.It Ev PKG_CONFIG_DONT_RELOCATE_PATHS +If set, disables the path relocation feature. +.It Ev PKG_CONFIG_FDO_SYSROOT_RULES +If set, follow the sysroot prefixing rules that freedesktop.org pkg-config uses. +.It Ev PKG_CONFIG_IGNORE_CONFLICTS +If set, ignore +.Ic Conflicts +rules in modules. +Has the same effect as the +.Fl -ignore-conflicts +option. +.It Ev PKG_CONFIG_LIBDIR +A colon-separated list of low-priority directories where +.Xr pc 5 +files are looked up. +The module search path is constructed by appending this list to +.Ev PKG_CONFIG_PATH , +which enjoys higher priority. +If +.Ev PKG_CONFIG_LIBDIR +is not defined, the default list compiled into the +.Nm +program from the +.Dv PKG_DEFAULT_PATH +preprocessor macro is appended instead. +If +.Ev PKG_CONFIG_LIBDIR +is defined but empty, nothing is appended. +.It Ev PKG_CONFIG_LOG +If set, log information about selected modules +to the file with the name stored in this variable. +For more details, see the +.Fl -log-file +command line option, which overrides this variable. +.It Ev PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH +Impose a limit on the allowed depth in the dependency graph. +This variable overrides the +.Fl -maximum-traverse-depth +option, but is overridden by the other options mentioned there. +.It Ev PKG_CONFIG_MSVC_SYNTAX +If set, use MSVC syntax for +.Fl -cflags , +.Fl -env , +and +.Fl -libs +output. +This variable has the same effect as the +.Fl -msvc-syntax +option. +If the preprocessor macro +.Dv PKGCONF_LITE +was defined during compilation, this variable is ignored. +.It Ev PKG_CONFIG_PATH +A colon-separated list of high-priority directories where +.Xr pc 5 +files are looked up. +The module search path is constructed +by prepending the directory specified with +.Fl -with-path , +if any, and unless +.Fl -env-only +is specified, by appending either +.Ev PKG_CONFIG_LIBDIR +or the compiled-in default directories with lower priority. +.It Ev PKG_CONFIG_PRELOADED_FILES +Colon-separated list of +.Xr pc 5 +files which are loaded before any other pkg-config files. +These packages are given highest priority over any other +.Xr pc 5 +files that would otherwise provide a given package. +.It Ev PKG_CONFIG_PURE_DEPGRAPH +If set, enables the same behaviour as the +.Fl -pure +flag. +.It Ev PKG_CONFIG_RELOCATE_PATHS +If set, this variable has the same effect as the +.Fl -define-prefix +option. +.It Ev PKG_CONFIG_SYSROOT_DIR +If set, this variable defines a +.Sq sysroot +directory, which will be prepended to every path variable +beginning with the prefix variable in a given +.Xr pc 5 +file. +Useful for cross compilation. +The value of this environment variable is also copied into the global variable +.Va pc_sysrootdir . +.It Ev PKG_CONFIG_SYSTEM_INCLUDE_PATH +Colon-separated list of include paths that are filtered out +and not printed by the +.Fl -cflags +and +.Fl -cflags-only-I +options because they are considered system include paths. +If not defined, the default list compiled into the +.Nm +program from the +.Dv SYSTEM_INCLUDEDIR +preprocessor macro is used instead. +This variable is a pkgconf-specific extension. +Any directories listed in the environment variables +.Ev CPATH , +.Ev C_INCLUDE_PATH , +.Ev CPLUS_INCLUDE_PATH , +and +.Ev OBJC_INCLUDE_PATH +are also filtered out. +.It Ev PKG_CONFIG_SYSTEM_LIBRARY_PATH +Colon-separated list of library paths that are filtered out +and not printed by the +.Fl -libs +and +.Fl -libs-only-L +options because they are considered system library paths. +If not defined, the default list compiled into the +.Nm +program from the +.Dv SYSTEM_LIBDIR +preprocessor macro is used instead. +This variable is a pkgconf-specific extension. +.It Ev PKG_CONFIG_TOP_BUILD_DIR +The value of the +.Va pc_top_builddir +global variable. +If this environment variable is not defined, the string +.Qq $(top_builddir) +is used as the value of +.Va pc_top_builddir . +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Displaying the CFLAGS of a package: +.Dl $ pkgconf --cflags foo +.Dl -fPIC -I/usr/include/foo +.Sh SEE ALSO +.Xr pc 5 , +.Xr pkg.m4 7 diff --git a/meson.build b/meson.build new file mode 100644 index 00000000000..30928a04c51 --- /dev/null +++ b/meson.build @@ -0,0 +1,199 @@ +project('pkgconf', 'c', + version : '2.5.1', + license : 'ISC', + default_options : ['c_std=c99'], + meson_version: '>=0.52', +) + +cc = meson.get_compiler('c') + +add_project_arguments( + '-D_BSD_SOURCE', + '-D_DEFAULT_SOURCE', + '-D_POSIX_C_SOURCE=200809L', + cc.get_supported_arguments( + '-Wimplicit-function-declaration', + '-Wmisleading-indentation', + ), + language : 'c', +) + +cdata = configuration_data() + +check_functions = [ + ['strlcat', 'string.h'], + ['strlcpy', 'string.h'], + ['strndup', 'string.h'], + ['strdup', 'string.h'], + ['strncasecmp', 'strings.h'], + ['strcasecmp', 'strings.h'], + ['reallocarray', 'stdlib.h'], + ['pledge', 'unistd.h'], + ['unveil', 'unistd.h'], +] + +foreach f : check_functions + name = f[0].to_upper().underscorify() + if cc.has_function(f[0], prefix : '#define _BSD_SOURCE\n#define _DEFAULT_SOURCE\n#define _POSIX_C_SOURCE 200809L\n#include <@0@>'.format(f[1])) and cc.has_header_symbol(f[1], f[0], prefix : '#define _BSD_SOURCE\n#define _DEFAULT_SOURCE\n#define _POSIX_C_SOURCE 200809L') + cdata.set('HAVE_@0@'.format(name), 1) + cdata.set('HAVE_DECL_@0@'.format(name), 1) + else + cdata.set('HAVE_DECL_@0@'.format(name), 0) + endif +endforeach + +default_path = [] +foreach f : ['libdir', 'datadir'] + default_path += [join_paths(get_option('prefix'), get_option(f), 'pkgconfig')] +endforeach + +personality_path = [] +foreach f : ['libdir', 'datadir'] + personality_path += [join_paths(get_option('prefix'), get_option(f), 'pkgconfig', 'personality.d')] +endforeach + +SYSTEM_LIBDIR = get_option('with-system-libdir') +if SYSTEM_LIBDIR != '' + cdata.set_quoted('SYSTEM_LIBDIR', SYSTEM_LIBDIR) +else + cdata.set_quoted('SYSTEM_LIBDIR', join_paths(get_option('prefix'), get_option('libdir'))) +endif +SYSTEM_INCLUDEDIR = get_option('with-system-includedir') +if SYSTEM_INCLUDEDIR != '' + cdata.set_quoted('SYSTEM_INCLUDEDIR', SYSTEM_INCLUDEDIR) +else + cdata.set_quoted('SYSTEM_INCLUDEDIR', join_paths(get_option('prefix'), get_option('includedir'))) +endif +cdata.set_quoted('PKG_DEFAULT_PATH', ':'.join(default_path)) +cdata.set_quoted('PERSONALITY_PATH', ':'.join(personality_path)) +cdata.set_quoted('PACKAGE_NAME', meson.project_name()) +cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) +cdata.set_quoted('PACKAGE_BUGREPORT', 'https://todo.sr.ht/~kaniini/pkgconf') +cdata.set('abs_top_srcdir', meson.current_source_dir()) +cdata.set('abs_top_builddir', meson.current_build_dir()) + + +subdir('libpkgconf') + +libtype = get_option('default_library') +if libtype == 'static' + build_static = '-DPKGCONFIG_IS_STATIC' +else + build_static = '-DPKGCONFIG_IS_NOT_STATIC' +endif + +libpkgconf = library('pkgconf', + 'libpkgconf/argvsplit.c', + 'libpkgconf/audit.c', + 'libpkgconf/buffer.c', + 'libpkgconf/bsdstubs.c', + 'libpkgconf/cache.c', + 'libpkgconf/client.c', + 'libpkgconf/dependency.c', + 'libpkgconf/fileio.c', + 'libpkgconf/fragment.c', + 'libpkgconf/parser.c', + 'libpkgconf/path.c', + 'libpkgconf/personality.c', + 'libpkgconf/pkg.c', + 'libpkgconf/queue.c', + 'libpkgconf/tuple.c', + c_args: ['-DLIBPKGCONF_EXPORT', build_static], + install : true, + version : '7.0.0', + soversion : '7', +) + +# For other projects using libpkgconfig as a subproject +dep_libpkgconf = declare_dependency( + link_with : libpkgconf, + include_directories : include_directories('.'), +) + +# If we have a new enough meson override the dependency so that only +# `dependency('libpkgconf')` is required from the consumer +if meson.version().version_compare('>= 0.54.0') + meson.override_dependency('libpkgconf', dep_libpkgconf) +endif + +pkg = import('pkgconfig') +pkg.generate(libpkgconf, + name : 'libpkgconf', + description : 'a library for accessing and manipulating development framework configuration', + url: 'http://github.com/pkgconf/pkgconf', + filebase : 'libpkgconf', + subdirs: ['pkgconf'], + extra_cflags : build_static +) + +cli_include = include_directories('cli') + +pkgconf_exe = executable('pkgconf', + 'cli/main.c', + 'cli/getopt_long.c', + 'cli/renderer-msvc.c', + link_with : libpkgconf, + c_args : build_static, + include_directories : cli_include, + install : true) + +bomtool_exe = executable('bomtool', + 'cli/bomtool/main.c', + 'cli/getopt_long.c', + link_with : libpkgconf, + c_args : build_static, + include_directories : cli_include, + install : true) + +with_tests = get_option('tests') +kyua_exe = find_program('kyua', required : with_tests, disabler : true, native : true) +atf_sh_exe = find_program('atf-sh', required : with_tests, disabler : true, native : true) +kyuafile = configure_file(input : 'Kyuafile.in', output : 'Kyuafile', configuration : cdata) +test('kyua', kyua_exe, args : ['--config=none', 'test', '--kyuafile', kyuafile, '--build-root', meson.current_build_dir()]) +subdir('tests') + +install_man('man/bomtool.1') +install_man('man/pkgconf.1') +install_man('man/pkg.m4.7') +install_man('man/pc.5') +install_man('man/pkgconf-personality.5') +install_data('pkg.m4', install_dir: 'share/aclocal') +install_data('AUTHORS', install_dir: 'share/doc/pkgconf') +install_data('README.md', install_dir: 'share/doc/pkgconf') + +if host_machine.system() == 'windows' + conf_data = configuration_data() + conf_data.set('VERSION', meson.project_version()) + conf_data.set('EXE', pkgconf_exe.full_path()) + conf_data.set('DLL', libpkgconf.full_path()) + if host_machine.cpu() != 'x86_64' + wixl_arch = 'x86' + else + wixl_arch = 'x64' + endif + conf_data.set('WIXL_ARCH', wixl_arch) + + python = find_program('python3') + wixl = find_program('wixl', required: false, version: '>= 0.105') + msi_filename = 'pkgconf-@0@-@1@.msi'.format(wixl_arch, meson.project_version()) + + wxsfile = configure_file(input: 'pkgconf.wxs.in', output: 'pkgconf.wxs', configuration: conf_data) + + if wixl.found() + licensefile = custom_target( + 'License.rtf', + input: 'COPYING', + output: 'License.rtf', + command: [python, files('txt2rtf.py'), '@INPUT@', '@OUTPUT@'], + ) + + msi = custom_target( + msi_filename, + input: [wxsfile, licensefile, pkgconf_exe], + output: msi_filename, + command: [wixl, '--arch', wixl_arch, '--ext', 'ui', '-o', msi_filename, wxsfile], + ) + + alias_target('msi', msi) + endif +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000000..e1360520bee --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,19 @@ +option( + 'tests', + type: 'feature', + description: 'Build tests which depends upon the kyua framework', +) + +option( + 'with-system-libdir', + type: 'string', + value: '', + description: 'Specify the system library directory (default {prefix}/{libdir})' +) + +option( + 'with-system-includedir', + type: 'string', + value: '', + description: 'Specify the system include directory (default {prefix}/{includedir})' +) diff --git a/pkg.m4 b/pkg.m4 new file mode 100644 index 00000000000..ec5a70da38f --- /dev/null +++ b/pkg.m4 @@ -0,0 +1,350 @@ +# pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- +# serial 13 (pkgconf) + +dnl Copyright © 2004 Scott James Remnant . +dnl Copyright © 2012-2015 Dan Nicholson +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, see . +dnl +dnl As a special exception to the GNU General Public License, if you +dnl distribute this file as part of a program that contains a +dnl configuration script generated by Autoconf, you may include it under +dnl the same distribution terms that you use for the rest of that +dnl program. + +dnl PKG_PREREQ(MIN-VERSION) +dnl ----------------------- +dnl Since: 0.29 +dnl +dnl Verify that the version of the pkg-config macros are at least +dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's +dnl installed version of pkg-config, this checks the developer's version +dnl of pkg.m4 when generating configure. +dnl +dnl To ensure that this macro is defined, also add: +dnl m4_ifndef([PKG_PREREQ], +dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) +dnl +dnl See the "Since" comment for each macro you use to see what version +dnl of the macros you require. +m4_defun([PKG_PREREQ], +[m4_define([PKG_MACROS_VERSION], [0.29.2]) +m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, + [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) +])dnl PKG_PREREQ + +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION], [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------- +dnl Since: 0.16 +dnl +dnl Search for the pkg-config tool and set the PKG_CONFIG variable to +dnl first found in the path. Checks that the version of pkg-config found +dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is +dnl used since that's the first version where most current features of +dnl pkg-config existed. +dnl +dnl If pkg-config is not found or older than specified, it will result +dnl in an empty PKG_CONFIG variable. To avoid widespread issues with +dnl scripts not checking it, ACTION-IF-NOT-FOUND defaults to aborting. +dnl You can specify [PKG_CONFIG=false] as an action instead, which would +dnl result in pkg-config tests failing, but no bogus error messages. +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi +if test -z "$PKG_CONFIG"; then + m4_default([$2], [AC_MSG_ERROR([pkg-config not found])]) +fi[]dnl +])dnl PKG_PROG_PKG_CONFIG + +dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------------------------------- +dnl Since: 0.18 +dnl +dnl Check to see whether a particular set of modules exists. Similar to +dnl PKG_CHECK_MODULES(), but does not set variables or print errors. +dnl +dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +dnl only at the first occurrence in configure.ac, so if the first place +dnl it's called might be skipped (such as if it is within an "if", you +dnl have to call PKG_CHECK_EXISTS manually +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +dnl --------------------------------------------- +dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting +dnl pkg_failed based on the result. +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])dnl _PKG_CONFIG + +dnl _PKG_SHORT_ERRORS_SUPPORTED +dnl --------------------------- +dnl Internal check to see if pkg-config supports short errors. +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])dnl _PKG_SHORT_ERRORS_SUPPORTED + + +dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl -------------------------------------------------------------- +dnl Since: 0.4.0 +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES might not happen, you should be sure to include an +dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $2]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])dnl PKG_CHECK_MODULES + + +dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------------------- +dnl Since: 0.29 +dnl +dnl Checks for existence of MODULES and gathers its build flags with +dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags +dnl and VARIABLE-PREFIX_LIBS from --libs. +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to +dnl include an explicit call to PKG_PROG_PKG_CONFIG in your +dnl configure.ac. +AC_DEFUN([PKG_CHECK_MODULES_STATIC], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +_save_PKG_CONFIG=$PKG_CONFIG +PKG_CONFIG="$PKG_CONFIG --static" +PKG_CHECK_MODULES($@) +PKG_CONFIG=$_save_PKG_CONFIG[]dnl +])dnl PKG_CHECK_MODULES_STATIC + + +dnl PKG_INSTALLDIR([DIRECTORY]) +dnl ------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable pkgconfigdir as the location where a module +dnl should install pkg-config .pc files. By default the directory is +dnl $libdir/pkgconfig, but the default can be changed by passing +dnl DIRECTORY. The user can override through the --with-pkgconfigdir +dnl parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_INSTALLDIR + + +dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) +dnl -------------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable noarch_pkgconfigdir as the location where a +dnl module should install arch-independent pkg-config .pc files. By +dnl default the directory is $datadir/pkgconfig, but the default can be +dnl changed by passing DIRECTORY. The user can override through the +dnl --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_NOARCH_INSTALLDIR + + +dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------- +dnl Since: 0.28 +dnl +dnl Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])dnl PKG_CHECK_VAR + +dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------ +dnl +dnl Prepare a "--with-" configure option using the lowercase +dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and +dnl PKG_CHECK_MODULES in a single macro. +AC_DEFUN([PKG_WITH_MODULES], +[ +m4_pushdef([with_arg], m4_tolower([$1])) + +m4_pushdef([description], + [m4_default([$5], [build with ]with_arg[ support])]) + +m4_pushdef([def_arg], [m4_default([$6], [auto])]) +m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) +m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) + +m4_case(def_arg, + [yes],[m4_pushdef([with_without], [--without-]with_arg)], + [m4_pushdef([with_without],[--with-]with_arg)]) + +AC_ARG_WITH(with_arg, + AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, + [AS_TR_SH([with_]with_arg)=def_arg]) + +AS_CASE([$AS_TR_SH([with_]with_arg)], + [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], + [auto],[PKG_CHECK_MODULES([$1],[$2], + [m4_n([def_action_if_found]) $3], + [m4_n([def_action_if_not_found]) $4])]) + +m4_popdef([with_arg]) +m4_popdef([description]) +m4_popdef([def_arg]) + +])dnl PKG_WITH_MODULES + +dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ----------------------------------------------- +dnl +dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES +dnl check._[VARIABLE-PREFIX] is exported as make variable. +AC_DEFUN([PKG_HAVE_WITH_MODULES], +[ +PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) + +AM_CONDITIONAL([HAVE_][$1], + [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) +])dnl PKG_HAVE_WITH_MODULES + +dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------------------ +dnl +dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after +dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make +dnl and preprocessor variable. +AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], +[ +PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) + +AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], + [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) +])dnl PKG_HAVE_DEFINE_WITH_MODULES diff --git a/pkgconf.wxs.in b/pkgconf.wxs.in new file mode 100644 index 00000000000..13ebb069a50 --- /dev/null +++ b/pkgconf.wxs.in @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Kyuafile.in b/tests/Kyuafile.in new file mode 100644 index 00000000000..f50b749fd71 --- /dev/null +++ b/tests/Kyuafile.in @@ -0,0 +1,14 @@ +syntax(2) + +test_suite('pkgconf') + +atf_test_program{name='basic'} +atf_test_program{name='requires'} +atf_test_program{name='regress'} +atf_test_program{name='parser'} +atf_test_program{name='sysroot'} +atf_test_program{name='conflicts'} +atf_test_program{name='version'} +atf_test_program{name='framework'} +atf_test_program{name='provides'} +atf_test_program{name='symlink'} diff --git a/tests/basic.sh b/tests/basic.sh new file mode 100755 index 00000000000..8debb32f2b3 --- /dev/null +++ b/tests/basic.sh @@ -0,0 +1,391 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + noargs \ + libs \ + libs_cflags \ + libs_cflags_version \ + libs_cflags_version_multiple \ + libs_cflags_version_alt \ + libs_cflags_version_different \ + libs_cflags_version_different_bad \ + libs_env \ + exists_nonexitent \ + nonexitent \ + exists_version \ + exists_version_bad \ + exists_version_bad2 \ + exists_version_bad3 \ + exists \ + exists2 \ + exists3 \ + exists_version_alt \ + exists_cflags \ + exists_cflags_env \ + uninstalled_bad \ + uninstalled \ + libs_intermediary \ + libs_circular1 \ + libs_circular2 \ + libs_circular_directpc \ + libs_static \ + libs_static_ordering \ + libs_metapackage \ + license_isc \ + license_noassertion \ + modversion_noflatten \ + pkg_config_path \ + nolibs \ + nocflags \ + arbitary_path \ + with_path \ + relocatable \ + single_depth_selectors \ + print_variables_env \ + variable_env \ + variable_no_recurse + +noargs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -s exit:1 -e ignore pkgconf +} + +libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lfoo\n" \ + pkgconf --libs foo +} + +libs_cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \ + pkgconf --cflags --libs foo +} + +atf_test_case basic_libs_cflags_version +libs_cflags_version_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \ + pkgconf --cflags --libs 'foo > 1.2' +} + +libs_cflags_version_multiple_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" \ + pkgconf --cflags --libs 'foo > 1.2 bar >= 1.3' +} + +libs_cflags_version_multiple_coma_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbar -lfoo\n" \ + pkgconf --cflags --libs 'foo > 1.2,bar >= 1.3' +} + +libs_cflags_version_alt_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \ + pkgconf --cflags --libs 'foo' '>' '1.2' +} + +libs_cflags_version_different_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lfoo\n" \ + pkgconf --cflags --libs 'foo' '!=' '1.3.0' +} + +atf_test_case basic_libs_cflags_version_different_bad +libs_cflags_version_different_bad_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + -e inline:"Package dependency requirement 'foo != 1.2.3' could not be satisfied.\nPackage 'foo' has version '1.2.3', required version is '!= 1.2.3'\n" \ + pkgconf --cflags --libs 'foo' '!=' '1.2.3' +} + +exists_nonexitent_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --exists nonexistant +} + +nonexitent_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf nonexistant +} + +exists_version_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --exists 'foo > 1.2' +} + +exists_version_bad_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --exists 'foo > 1.2.3' +} + +exists_version_alt_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --exists 'foo' '>' '1.2' +} + +uninstalled_bad_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --uninstalled 'foo' +} + +uninstalled_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --uninstalled 'omg' +} + +exists_version_bad2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --exists 'foo >= ' +} + +exists_version_bad3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --exists 'tilde >= 1.0.0' +} + +exists_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --exists 'tilde = 1.0.0~rc1' +} + +exists2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --exists 'tilde <= 1.0.0' +} + +exists3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --exists '' 'foo' +} + +libs_intermediary_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lintermediary-1 -lintermediary-2 -lfoo -lbar -lbaz\n" \ + pkgconf --libs intermediary-1 intermediary-2 +} + +libs_circular2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"circular-1: breaking circular reference (circular-1 -> circular-2 -> circular-1)\n" \ + pkgconf circular-2 --validate +} + +libs_circular1_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"circular-3: breaking circular reference (circular-3 -> circular-1 -> circular-3)\n" \ + pkgconf circular-1 --validate +} + +libs_circular_directpc_body() +{ + atf_check \ + -o inline:"-lcircular-3 -lcircular-1 -lcircular-2\n" \ + pkgconf --libs ${selfdir}/lib1/circular-3.pc +} + +libs_static_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"/libfoo.a -pthread\n" \ + pkgconf --libs static-archive-libs +} + +libs_static_ordering_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lbar -lfoo\n" \ + pkgconf --libs foo bar +} + +libs_metapackage_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lbar -lfoo\n" \ + pkgconf --static --libs metapackage-3 +} + +pkg_config_path_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1${PATH_SEP}${selfdir}/lib2" + atf_check \ + -o inline:"-L/test/lib -lfoo\n" \ + pkgconf --libs foo + atf_check \ + -o inline:"-L/test/lib -lbar -lfoo\n" \ + pkgconf --libs bar +} + +with_path_body() +{ + atf_check \ + -o inline:"-L/test/lib -lfoo\n" \ + pkgconf --with-path=${selfdir}/lib1 --with-path=${selfdir}/lib2 --libs foo + atf_check \ + -o inline:"-L/test/lib -lbar -lfoo\n" \ + pkgconf --with-path=${selfdir}/lib1 --with-path=${selfdir}/lib2 --libs bar +} + +nolibs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"\n" \ + pkgconf --libs nolib +} + +nocflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"\n" \ + pkgconf --cflags nocflag +} + +arbitary_path_body() +{ + cp ${selfdir}/lib1/foo.pc . + atf_check \ + -o inline:"-L/test/lib -lfoo\n" \ + pkgconf --libs foo.pc +} + +relocatable_body() +{ + basedir=$(pkgconf --relocate ${selfdir}) + atf_check \ + -o inline:"${basedir}/lib-relocatable\n" \ + pkgconf --define-prefix --variable=prefix ${basedir}/lib-relocatable/lib/pkgconfig/foo.pc +} + +single_depth_selectors_body() +{ + export PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH=1 + atf_check \ + -o inline:"foo\n" \ + pkgconf --with-path=${selfdir}/lib3 --print-requires bar +} + +license_isc_body() +{ + atf_check \ + -o inline:"foo: ISC\n" \ + pkgconf --with-path=${selfdir}/lib1 --license foo +} + +license_noassertion_body() +{ + atf_check \ + -o inline:"bar: NOASSERTION\nfoo: ISC\n" \ + pkgconf --with-path=${selfdir}/lib1 --license bar +} + +modversion_noflatten_body() +{ + atf_check \ + -o inline:"1.3\n" \ + pkgconf --with-path=${selfdir}/lib1 --modversion bar +} + +exists_cflags_body() +{ + atf_check \ + -o inline:"-DHAVE_FOO\n" \ + pkgconf --with-path=${selfdir}/lib1 --cflags --exists-cflags --fragment-filter=D foo +} + +exists_cflags_env_body() +{ + atf_check \ + -o inline:"FOO_CFLAGS='-DHAVE_FOO'\n" \ + pkgconf --with-path=${selfdir}/lib1 --cflags --exists-cflags --fragment-filter=D --env=FOO foo +} + +libs_env_body() +{ + atf_check \ + -o inline:"FOO_LIBS='-L/test/lib -lfoo'\n" \ + pkgconf --with-path=${selfdir}/lib1 --libs --env=FOO foo +} + +print_variables_env_body() +{ + atf_check \ + -o inline:"FOO_CFLAGS='-fPIC -I/test/include/foo'\nFOO_LIBS='-L/test/lib -lfoo'\nFOO_INCLUDEDIR='/test/include'\nFOO_LIBDIR='/test/lib'\nFOO_EXEC_PREFIX='/test'\nFOO_PREFIX='/test'\nFOO_PCFILEDIR='${selfdir}/lib1'\n" \ + pkgconf --with-path=${selfdir}/lib1 --env=FOO --print-variables --cflags --libs foo + +} + +variable_env_body() +{ + atf_check \ + -o inline:"FOO_INCLUDEDIR='/test/include'\n" \ + pkgconf --with-path=${selfdir}/lib1 --env=FOO --variable=includedir foo +} + +variable_no_recurse_body() +{ + atf_check \ + -o inline:"/test/include\n" \ + pkgconf --with-path=${selfdir}/lib1 --variable=includedir bar +} diff --git a/tests/builtins.sh b/tests/builtins.sh new file mode 100755 index 00000000000..cbdea37e0c5 --- /dev/null +++ b/tests/builtins.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + modversion \ + variable \ + define_variable \ + global_variable + +modversion_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"1.0.1 \n" \ + pkgconf --modversion pkg-config +} + +variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"/test \n" \ + pkgconf --variable=prefix foo +} + +define_variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"/test2 \n" \ + pkgconf --define-variable=prefix=/test2 --variable=prefix foo +} + +global_variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"${selfdir}/lib1 \n" + pkgconf --exists -foo +} + +argv_parse_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-llib-1 -pthread /test/lib/lib2.so \n" \ + pkgconf --libs argv-parse-3 +} + +tilde_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L~ -ltilde \n" \ + pkgconf --libs tilde-quoting + atf_check \ + -o inline:"-I~ \n" \ + pkgconf --cflags tilde-quoting +} + +paren_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L\$(libdir) -ltilde \n" \ + pkgconf --libs paren-quoting +} diff --git a/tests/conflicts.sh b/tests/conflicts.sh new file mode 100755 index 00000000000..da6396f6b42 --- /dev/null +++ b/tests/conflicts.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + libs \ + ignore + +libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lconflicts\n" \ + pkgconf --libs conflicts +} + +ignore_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lconflicts\n" \ + pkgconf --ignore-conflicts --libs conflicts +} diff --git a/tests/framework.sh b/tests/framework.sh new file mode 100755 index 00000000000..36769233574 --- /dev/null +++ b/tests/framework.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + libs + +libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-F/test/lib -framework framework-1\n" \ + pkgconf --libs framework-1 + atf_check \ + -o inline:"-F/test/lib -framework framework-2 -framework framework-1\n" \ + pkgconf --libs framework-2 + atf_check \ + -o inline:"-F/test/lib -framework framework-2 -framework framework-1\n" \ + pkgconf --libs framework-1 framework-2 +} diff --git a/tests/lib-relocatable/lib/pkgconfig/foo.pc b/tests/lib-relocatable/lib/pkgconfig/foo.pc new file mode 100644 index 00000000000..f154eb76ccf --- /dev/null +++ b/tests/lib-relocatable/lib/pkgconfig/foo.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo +Cflags.private: -DFOO_STATIC diff --git a/tests/lib1/argv-parse-2.pc b/tests/lib1/argv-parse-2.pc new file mode 100644 index 00000000000..cfc0fcf904b --- /dev/null +++ b/tests/lib1/argv-parse-2.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: argv-parse-2 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -llib-1 -pthread ${libdir}/lib2.so +Cflags: diff --git a/tests/lib1/argv-parse-3.pc b/tests/lib1/argv-parse-3.pc new file mode 100644 index 00000000000..b115b300120 --- /dev/null +++ b/tests/lib1/argv-parse-3.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: argv-parse-3 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -llib-1 \ + -pthread ${libdir}/lib2.so +Cflags: diff --git a/tests/lib1/argv-parse.pc b/tests/lib1/argv-parse.pc new file mode 100644 index 00000000000..d445ae78e7f --- /dev/null +++ b/tests/lib1/argv-parse.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: argv-parse +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -llib-3 -llib-1 -llib-2 -lpthread +Cflags: diff --git a/tests/lib1/bar.pc b/tests/lib1/bar.pc new file mode 100644 index 00000000000..9157d06b5fd --- /dev/null +++ b/tests/lib1/bar.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: bar +Description: Another pkg-config test +Version: 1.3 +Libs: -L${libdir} -lbar +Requires: foo diff --git a/tests/lib1/baz.pc b/tests/lib1/baz.pc new file mode 100644 index 00000000000..d9cb258020b --- /dev/null +++ b/tests/lib1/baz.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: bar +Description: Another pkg-config test (with private Requires, ha!) +Version: 1.3 +Libs: -L${libdir} -lbaz +Libs.private: -L${libdir} -lzee +Requires.private: foo diff --git a/tests/lib1/billion-laughs.pc b/tests/lib1/billion-laughs.pc new file mode 100644 index 00000000000..ad1ee7948b4 --- /dev/null +++ b/tests/lib1/billion-laughs.pc @@ -0,0 +1,13 @@ +v9=lol +v8=${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9} +v7=${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8} +v6=${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7} +v5=${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6} +v4=${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5} +v3=${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4} +v2=${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3} +v1=${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2} +v0=${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1} +Name: One Billion Laughs +Version: ${v0} +Description: Don't install this! diff --git a/tests/lib1/c-comment.pc b/tests/lib1/c-comment.pc new file mode 100644 index 00000000000..ed776a1f33e --- /dev/null +++ b/tests/lib1/c-comment.pc @@ -0,0 +1,17 @@ +/* + This is a C-style comment, which technically isn't allowed, but + some pkg-config files use them. + */ + +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: c-comment +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo +Cflags.private: -DFOO_STATIC +License: ISC diff --git a/tests/lib1/case-sensitivity.pc b/tests/lib1/case-sensitivity.pc new file mode 100644 index 00000000000..9618061c485 --- /dev/null +++ b/tests/lib1/case-sensitivity.pc @@ -0,0 +1,7 @@ +foo=3 +Foo=4 + +Name: case-sensitivity +Description: Package for testing case-sensitivity +Version: 1 +Requires: foo < ${foo} diff --git a/tests/lib1/cflags-internal.pc b/tests/lib1/cflags-internal.pc new file mode 100644 index 00000000000..500e566df71 --- /dev/null +++ b/tests/lib1/cflags-internal.pc @@ -0,0 +1,9 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: cflags-internal +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -I${includedir}/foo diff --git a/tests/lib1/cflags-libs-only.pc b/tests/lib1/cflags-libs-only.pc new file mode 100644 index 00000000000..718e7cd9501 --- /dev/null +++ b/tests/lib1/cflags-libs-only.pc @@ -0,0 +1,10 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: cflags-libs-only +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -I${includedir}/foo diff --git a/tests/lib1/cflags-libs-private-a.pc b/tests/lib1/cflags-libs-private-a.pc new file mode 100644 index 00000000000..fbb7742ba2e --- /dev/null +++ b/tests/lib1/cflags-libs-private-a.pc @@ -0,0 +1,7 @@ +Name: cflags-libs-private-a +Version: 1 +Description: test case for issue #370 +Cflags: +Libs: +Requires.private: cflags-libs-private-b + diff --git a/tests/lib1/cflags-libs-private-b.pc b/tests/lib1/cflags-libs-private-b.pc new file mode 100644 index 00000000000..0d5e5a45bfa --- /dev/null +++ b/tests/lib1/cflags-libs-private-b.pc @@ -0,0 +1,7 @@ +Name: cflags-libs-private-b +Version: 1 +Description: test case for issue #370 +Cflags: +Libs: +Requires: cflags-libs-private-c + diff --git a/tests/lib1/cflags-libs-private-c.pc b/tests/lib1/cflags-libs-private-c.pc new file mode 100644 index 00000000000..7efc38e4bb9 --- /dev/null +++ b/tests/lib1/cflags-libs-private-c.pc @@ -0,0 +1,6 @@ +Name: cflags-libs-private-c +Version: 1 +Description: test case for issue #370 +Cflags: +Libs: -lc + diff --git a/tests/lib1/cflags-whitespace-trailing.pc b/tests/lib1/cflags-whitespace-trailing.pc new file mode 100644 index 00000000000..2e660353b00 --- /dev/null +++ b/tests/lib1/cflags-whitespace-trailing.pc @@ -0,0 +1,4 @@ +Name: CFlags Trailing Whitespace Bug +Description: Demonstrates problems with -I with spaces +Version: 1 +Cflags: -I/usr/include -I/usr/include/foo diff --git a/tests/lib1/cflags-whitespace.pc b/tests/lib1/cflags-whitespace.pc new file mode 100644 index 00000000000..4cd1cd164aa --- /dev/null +++ b/tests/lib1/cflags-whitespace.pc @@ -0,0 +1,4 @@ +Name: CFlags Whitespace Bug +Description: Demonstrates problems with -I with spaces and sysroot munging +Version: 1 +Cflags: -I /opt/bad/include diff --git a/tests/lib1/child-prefix/pkgconfig/child-prefix-1.pc b/tests/lib1/child-prefix/pkgconfig/child-prefix-1.pc new file mode 100644 index 00000000000..0d32d89d925 --- /dev/null +++ b/tests/lib1/child-prefix/pkgconfig/child-prefix-1.pc @@ -0,0 +1,11 @@ +prefix=/usr +exec_prefix=/usr +libdir=${prefix}/lib64 +includedir=${prefix}/include + +Name: child-prefix-1 +Description: child prefix 1 test data +Requires: +Version: 1.0 +Libs: -L${libdir} -lchild-prefix-1 +Cflags: -I${includedir}/child-prefix-1 diff --git a/tests/lib1/circular-1.pc b/tests/lib1/circular-1.pc new file mode 100644 index 00000000000..bbdb9f79cc8 --- /dev/null +++ b/tests/lib1/circular-1.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: circular-1 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lcircular-1 +Requires: circular-2 +Cflags: diff --git a/tests/lib1/circular-2.pc b/tests/lib1/circular-2.pc new file mode 100644 index 00000000000..2086834a6b8 --- /dev/null +++ b/tests/lib1/circular-2.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: circular-2 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lcircular-2 +Requires: circular-3 +Cflags: diff --git a/tests/lib1/circular-3.pc b/tests/lib1/circular-3.pc new file mode 100644 index 00000000000..e8b8e77a6c5 --- /dev/null +++ b/tests/lib1/circular-3.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: circular-3 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lcircular-3 +Requires: circular-1 +Cflags: diff --git a/tests/lib1/comments-in-fields.pc b/tests/lib1/comments-in-fields.pc new file mode 100644 index 00000000000..8e65a6557f4 --- /dev/null +++ b/tests/lib1/comments-in-fields.pc @@ -0,0 +1,9 @@ +Name: comments-in-fields +Description: Comments in fields test case +URL: http://pkgconf.org/ +Version: 0 +Requires: # foo +Requires.private: foo +Libs: -lfoo # -lbar +Libs.private: -lbar +Cflags: -I${includedir} diff --git a/tests/lib1/comments.pc b/tests/lib1/comments.pc new file mode 100644 index 00000000000..be83b3e63b4 --- /dev/null +++ b/tests/lib1/comments.pc @@ -0,0 +1,6 @@ +Name: comments +Description: Portable CD-ROM I/O library +Version: 0 +#Requires: bar-2.0 +Libs: -lfoo +Cflags: -I${includedir} diff --git a/tests/lib1/conflicts.pc b/tests/lib1/conflicts.pc new file mode 100644 index 00000000000..bec486982d6 --- /dev/null +++ b/tests/lib1/conflicts.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: conflicts +Description: A testing pkg-config file +Conflicts: foo <= 1.3 +Version: 1.2.3 +Libs: -L${libdir} -lconflicts +Cflags: -fPIC -I${includedir}/conflicts diff --git a/tests/lib1/depgraph-break.pc b/tests/lib1/depgraph-break.pc new file mode 100644 index 00000000000..61e00cc62bc --- /dev/null +++ b/tests/lib1/depgraph-break.pc @@ -0,0 +1,12 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: depgraph-break +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo +Cflags.private: -DFOO_STATIC +Requires: nonexistant diff --git a/tests/lib1/dos-lineendings.pc b/tests/lib1/dos-lineendings.pc new file mode 100644 index 00000000000..cf169f159c7 --- /dev/null +++ b/tests/lib1/dos-lineendings.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: dos-lineendings +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir}/dos-lineendings -ldos-lineendings +Cflags: -fPIC -I${includedir}/dos-lineendings diff --git a/tests/lib1/empty-tuple.pc b/tests/lib1/empty-tuple.pc new file mode 100644 index 00000000000..a1794954a4f --- /dev/null +++ b/tests/lib1/empty-tuple.pc @@ -0,0 +1,6 @@ +xcflags= + +Name: empty-tuple +Description: testing file +Version: 1 +CFlags: ${xcflags} diff --git a/tests/lib1/escaped-backslash.pc b/tests/lib1/escaped-backslash.pc new file mode 100644 index 00000000000..6abcbb06459 --- /dev/null +++ b/tests/lib1/escaped-backslash.pc @@ -0,0 +1,4 @@ +Name: escaped-backslash +Version: 1 +Description: test package for backslash escape scenario +Cflags: -IC:\\A diff --git a/tests/lib1/explicit-sysroot.pc b/tests/lib1/explicit-sysroot.pc new file mode 100644 index 00000000000..63c7ca042bd --- /dev/null +++ b/tests/lib1/explicit-sysroot.pc @@ -0,0 +1,7 @@ +prefix=/usr +datarootdir=${prefix}/share +pkgdatadir=${pc_sysrootdir}/${datarootdir}/test + +Name: Test +Description: Testing pc_sysrootdir auto-expansion +Version: 1.0 diff --git a/tests/lib1/flag-order-1.pc b/tests/lib1/flag-order-1.pc new file mode 100644 index 00000000000..d84175197b1 --- /dev/null +++ b/tests/lib1/flag-order-1.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +cflags=-I${includedir} + +Name: flag order 1 +Description: test for flag ordering +Version: 1 +Libs: -L${libdir} -Bdynamic -lfoo -Bstatic -lbar diff --git a/tests/lib1/flag-order-3.pc b/tests/lib1/flag-order-3.pc new file mode 100644 index 00000000000..d813f50dbf4 --- /dev/null +++ b/tests/lib1/flag-order-3.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +cflags=-I${includedir} + +Name: flag order 3 +Description: test for flag ordering +Version: 1 +Libs: -L${libdir} -Wl,--start-group -lfoo -lbar -Wl,--end-group diff --git a/tests/lib1/foo.pc b/tests/lib1/foo.pc new file mode 100644 index 00000000000..daef9f9fa95 --- /dev/null +++ b/tests/lib1/foo.pc @@ -0,0 +1,12 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo +Cflags.private: -DFOO_STATIC +License: ISC diff --git a/tests/lib1/foobar.pc b/tests/lib1/foobar.pc new file mode 100644 index 00000000000..e9aa8433760 --- /dev/null +++ b/tests/lib1/foobar.pc @@ -0,0 +1,12 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foobar +Description: A testing pkg-config file +Version: 3.2.1 +Libs: -L${libdir} -lfoobar +Cflags: -fPIC -I${includedir}/foobar +Cflags.private: -DFOOBAR_STATIC +License: ISC diff --git a/tests/lib1/fragment-collision-1.pc b/tests/lib1/fragment-collision-1.pc new file mode 100644 index 00000000000..201bcb0a5f9 --- /dev/null +++ b/tests/lib1/fragment-collision-1.pc @@ -0,0 +1,4 @@ +Name: fragment-collision-1 +Version: 0 +Description: fragment collision test package +Cflags: -D_THREAD_SAFE -pthread diff --git a/tests/lib1/fragment-collision-2.pc b/tests/lib1/fragment-collision-2.pc new file mode 100644 index 00000000000..2e6feb4a61c --- /dev/null +++ b/tests/lib1/fragment-collision-2.pc @@ -0,0 +1,4 @@ +Name: fragment-collision-2 +Version: 0 +Description: fragment collision test package +CFlags: -D_FOO diff --git a/tests/lib1/fragment-collision-intermediary.pc b/tests/lib1/fragment-collision-intermediary.pc new file mode 100644 index 00000000000..1ddb6f502cf --- /dev/null +++ b/tests/lib1/fragment-collision-intermediary.pc @@ -0,0 +1,5 @@ +Name: fragment-collision-intermediary +Version: 0 +Description: fragment collision test package +Requires.private: fragment-collision-1, fragment-collision-2 +CFlags: -D_BAR diff --git a/tests/lib1/fragment-collision.pc b/tests/lib1/fragment-collision.pc new file mode 100644 index 00000000000..c58267513da --- /dev/null +++ b/tests/lib1/fragment-collision.pc @@ -0,0 +1,6 @@ +Name: fragment-collision +Version: 0 +Description: fragment collision test package +Requires: fragment-collision-2, fragment-collision-intermediary +Requires.private: fragment-collision-1 +Cflags: -D_BAZ diff --git a/tests/lib1/fragment-comment.pc b/tests/lib1/fragment-comment.pc new file mode 100644 index 00000000000..dfdd4f3b1dc --- /dev/null +++ b/tests/lib1/fragment-comment.pc @@ -0,0 +1,4 @@ +Name: fragment-comment +Description: Test case for issue #215 +Version: 1.0 +Cflags: kuku=\#ttt \ No newline at end of file diff --git a/tests/lib1/fragment-escaping-1.pc b/tests/lib1/fragment-escaping-1.pc new file mode 100644 index 00000000000..b147951e502 --- /dev/null +++ b/tests/lib1/fragment-escaping-1.pc @@ -0,0 +1,5 @@ +Name: fragment-escaping-1 +Version: 0 +Description: fragment escaping test +Cflags: "-IC:\\D E" +Libs: "-LC:\\D E" -lE diff --git a/tests/lib1/fragment-escaping-2.pc b/tests/lib1/fragment-escaping-2.pc new file mode 100644 index 00000000000..12e03b3482f --- /dev/null +++ b/tests/lib1/fragment-escaping-2.pc @@ -0,0 +1,4 @@ +Name: fragment-escaping-2 +Version: 0 +Description: fragment escaping test +Cflags: '-IC:\D E' diff --git a/tests/lib1/fragment-escaping-3.pc b/tests/lib1/fragment-escaping-3.pc new file mode 100644 index 00000000000..0f002ddb084 --- /dev/null +++ b/tests/lib1/fragment-escaping-3.pc @@ -0,0 +1,4 @@ +Name: fragment-escaping-3 +Version: 0 +Description: fragment escaping test +Cflags: -IC:\\D\ E diff --git a/tests/lib1/fragment-group-a.pc b/tests/lib1/fragment-group-a.pc new file mode 100644 index 00000000000..0e3966eab76 --- /dev/null +++ b/tests/lib1/fragment-group-a.pc @@ -0,0 +1,4 @@ +Name: fragment-group-a +Version: 1.0 +Description: Test fixture for fragment groups +Libs: -Wl,--start-group -la -lb -Wl,--end-group diff --git a/tests/lib1/fragment-group-b.pc b/tests/lib1/fragment-group-b.pc new file mode 100644 index 00000000000..ac7dda7223a --- /dev/null +++ b/tests/lib1/fragment-group-b.pc @@ -0,0 +1,4 @@ +Name: fragment-group-b +Version: 1.0 +Description: Test fixture for fragment groups +Libs: -nodefaultlibs diff --git a/tests/lib1/fragment-group-c.pc b/tests/lib1/fragment-group-c.pc new file mode 100644 index 00000000000..f79965be0f1 --- /dev/null +++ b/tests/lib1/fragment-group-c.pc @@ -0,0 +1,4 @@ +Name: fragment-group-c +Version: 1.0 +Description: Test fixture for fragment groups +Libs: -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections diff --git a/tests/lib1/fragment-groups-2.pc b/tests/lib1/fragment-groups-2.pc new file mode 100644 index 00000000000..f73f70698f5 --- /dev/null +++ b/tests/lib1/fragment-groups-2.pc @@ -0,0 +1,4 @@ +Name: fragment-groups-2 +Version: 1.0 +Description: Test fixture for fragment groups +Requires: fragment-group-a, fragment-group-b, fragment-group-c diff --git a/tests/lib1/fragment-groups.pc b/tests/lib1/fragment-groups.pc new file mode 100644 index 00000000000..9645f9e9719 --- /dev/null +++ b/tests/lib1/fragment-groups.pc @@ -0,0 +1,4 @@ +Name: fragment-groups +Version: 1.0 +Description: Test fixture for fragment groups +Libs: -Wl,--start-group -la -lb -Wl,--end-group -nodefaultlibs -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections diff --git a/tests/lib1/fragment-quoting-2.pc b/tests/lib1/fragment-quoting-2.pc new file mode 100644 index 00000000000..49c6ac79ce7 --- /dev/null +++ b/tests/lib1/fragment-quoting-2.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: fragment-quoting +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -fPIC -I${includedir}/foo -DQUOTED="${prefix}/share/doc" +Cflags.private: -DFOO_STATIC diff --git a/tests/lib1/fragment-quoting-3.pc b/tests/lib1/fragment-quoting-3.pc new file mode 100644 index 00000000000..4ff59fc37e0 --- /dev/null +++ b/tests/lib1/fragment-quoting-3.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: fragment-quoting +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -fPIC -I${includedir}/foo -DQUOTED=\"${prefix}/share/doc\" +Cflags.private: -DFOO_STATIC diff --git a/tests/lib1/fragment-quoting-5.pc b/tests/lib1/fragment-quoting-5.pc new file mode 100644 index 00000000000..22df0b3afba --- /dev/null +++ b/tests/lib1/fragment-quoting-5.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: fragment-quoting-5 +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -fPIC -I${includedir}/foo -DQUOTED='${prefix}/share/doc' +Cflags.private: -DFOO_STATIC diff --git a/tests/lib1/fragment-quoting-7.pc b/tests/lib1/fragment-quoting-7.pc new file mode 100644 index 00000000000..902e2f634e7 --- /dev/null +++ b/tests/lib1/fragment-quoting-7.pc @@ -0,0 +1,4 @@ +Name: fragment-quoting-7 +Description: A testing pkg-config file, this time from Go +Version: 1.2.3 +Cflags: -Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\ world diff --git a/tests/lib1/fragment-quoting.pc b/tests/lib1/fragment-quoting.pc new file mode 100644 index 00000000000..5d8e70b57f8 --- /dev/null +++ b/tests/lib1/fragment-quoting.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: fragment-quoting +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -fPIC -I${includedir}/foo -DQUOTED='"${prefix}/share/doc"' +Cflags.private: -DFOO_STATIC diff --git a/tests/lib1/framework-1.pc b/tests/lib1/framework-1.pc new file mode 100644 index 00000000000..fcaa0081483 --- /dev/null +++ b/tests/lib1/framework-1.pc @@ -0,0 +1,9 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: framework-1 +Description: Another pkg-config test +Version: 1.3 +Libs: -F${libdir} -framework framework-1 diff --git a/tests/lib1/framework-2.pc b/tests/lib1/framework-2.pc new file mode 100644 index 00000000000..793655a6cc3 --- /dev/null +++ b/tests/lib1/framework-2.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: framework-2 +Description: Another pkg-config test +Version: 1.3 +Libs: -F${libdir} -framework framework-2 +Requires: framework-1 diff --git a/tests/lib1/idirafter-ordering.pc b/tests/lib1/idirafter-ordering.pc new file mode 100644 index 00000000000..c42f7022736 --- /dev/null +++ b/tests/lib1/idirafter-ordering.pc @@ -0,0 +1,4 @@ +Name: Bad +Description: Demonstrates problems with -idirafter in old pkg-config; see also https://bugs.freedesktop.org/show_bug.cgi?id=23480 +Version: 1 +Cflags: -I/opt/bad/include1 -idirafter -I/opt/bad/include2 -I/opt/bad/include3 diff --git a/tests/lib1/idirafter.pc b/tests/lib1/idirafter.pc new file mode 100644 index 00000000000..6c37c8bffed --- /dev/null +++ b/tests/lib1/idirafter.pc @@ -0,0 +1,4 @@ +Name: Bad +Description: Demonstrates problems with -idirafter in both old pkg-config and current pkgconf; see also https://bugs.freedesktop.org/show_bug.cgi?id=97337 +Version: 1 +Cflags: -idirafter /opt/bad/include -idirafter /opt/bad2/include diff --git a/tests/lib1/incomplete.pc b/tests/lib1/incomplete.pc new file mode 100644 index 00000000000..40f0d91b9fc --- /dev/null +++ b/tests/lib1/incomplete.pc @@ -0,0 +1,4 @@ +Name: incomplete +Description: incomplete package without CFLAGS/LIBS entries (freedesktop #54271) +Version: 1.2.3 + diff --git a/tests/lib1/intermediary-1.pc b/tests/lib1/intermediary-1.pc new file mode 100644 index 00000000000..38ee4830ca3 --- /dev/null +++ b/tests/lib1/intermediary-1.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: intermediary-1 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lintermediary-1 -lfoo -lbar -lbaz +Cflags: diff --git a/tests/lib1/intermediary-2.pc b/tests/lib1/intermediary-2.pc new file mode 100644 index 00000000000..188ce6b0955 --- /dev/null +++ b/tests/lib1/intermediary-2.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: intermediary-2 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lintermediary-2 -lfoo -lbar -lbaz +Cflags: diff --git a/tests/lib1/isystem.pc b/tests/lib1/isystem.pc new file mode 100644 index 00000000000..98416b54ff3 --- /dev/null +++ b/tests/lib1/isystem.pc @@ -0,0 +1,4 @@ +Name: Bad +Description: Demonstrates problems with -isystem in both old pkg-config and current pkgconf; see also https://bugs.freedesktop.org/show_bug.cgi?id=72584 +Version: 1 +Cflags: -isystem /opt/bad/include -isystem /opt/bad2/include diff --git a/tests/lib1/malformed-1.pc b/tests/lib1/malformed-1.pc new file mode 100644 index 00000000000..4f4a700f017 --- /dev/null +++ b/tests/lib1/malformed-1.pc @@ -0,0 +1,2 @@ +prefix=/usr\nName: GKrellM\nDescription: Extensible GTK system monitoring application\nVersion: 2.3.7\nRequires: gtk+-2.0 >= 2.4.0\nCflags: -I/usr/include\n + diff --git a/tests/lib1/malformed-quoting.pc b/tests/lib1/malformed-quoting.pc new file mode 100644 index 00000000000..b8a324edadd --- /dev/null +++ b/tests/lib1/malformed-quoting.pc @@ -0,0 +1,4 @@ +Name: malformed-quoting +Version: 1 +Description: None. +Cflags: '-I/ABC diff --git a/tests/lib1/malformed-version.pc b/tests/lib1/malformed-version.pc new file mode 100644 index 00000000000..41222cc358b --- /dev/null +++ b/tests/lib1/malformed-version.pc @@ -0,0 +1,5 @@ +Name: malformed-version +Version: 3.922 2018-03-17 +Description: None. +Cflags: -Ifoo +Libs: -lbar diff --git a/tests/lib1/metapackage-1.pc b/tests/lib1/metapackage-1.pc new file mode 100644 index 00000000000..bb59db1a740 --- /dev/null +++ b/tests/lib1/metapackage-1.pc @@ -0,0 +1,6 @@ +Name: metapackage-1 +Version: 0.1 +Description: metapackage for testing purposes +Requires.private: metapackage-2 +Libs: -lmetapackage-1 +Cflags: -I/metapackage-1 diff --git a/tests/lib1/metapackage-2.pc b/tests/lib1/metapackage-2.pc new file mode 100644 index 00000000000..d8bf7d6b37a --- /dev/null +++ b/tests/lib1/metapackage-2.pc @@ -0,0 +1,5 @@ +Name: metapackage-2 +Version: 0.1 +Description: metapackage for testing purposes +Libs: -lmetapackage-2 +Cflags: -I/metapackage-2 diff --git a/tests/lib1/metapackage-3.pc b/tests/lib1/metapackage-3.pc new file mode 100644 index 00000000000..111954a04fc --- /dev/null +++ b/tests/lib1/metapackage-3.pc @@ -0,0 +1,4 @@ +Name: metapackage-3 +Version: 0.1 +Description: metapackage for testing purposes +Requires.private: bar diff --git a/tests/lib1/metapackage.pc b/tests/lib1/metapackage.pc new file mode 100644 index 00000000000..3ce0db354a3 --- /dev/null +++ b/tests/lib1/metapackage.pc @@ -0,0 +1,4 @@ +Name: metapackage +Version: 0.1 +Description: metapackage for testing purposes +Requires: metapackage-1, metapackage-2 diff --git a/tests/lib1/missing-require.pc b/tests/lib1/missing-require.pc new file mode 100644 index 00000000000..ede148d85e8 --- /dev/null +++ b/tests/lib1/missing-require.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: missing-require +Description: A testing pkg-config file +Version: 1.2.3 +Requires.private: missing +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo diff --git a/tests/lib1/multiline-bogus.pc b/tests/lib1/multiline-bogus.pc new file mode 100644 index 00000000000..c7f00653c7c --- /dev/null +++ b/tests/lib1/multiline-bogus.pc @@ -0,0 +1,9 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: multiline +Description: A multiline +description blah blah blah +Version: 1.2.3 diff --git a/tests/lib1/multiline.pc b/tests/lib1/multiline.pc new file mode 100644 index 00000000000..75b5902f894 --- /dev/null +++ b/tests/lib1/multiline.pc @@ -0,0 +1,9 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: multiline +Description: A multiline \ + description +Version: 1.2.3 diff --git a/tests/lib1/no-trailing-newline.pc b/tests/lib1/no-trailing-newline.pc new file mode 100644 index 00000000000..eaf2f8f2191 --- /dev/null +++ b/tests/lib1/no-trailing-newline.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: no-trailing-newline +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir}/no-trailing-newline -lno-trailing-newline +Cflags: -I${includedir}/no-trailing-newline diff --git a/tests/lib1/nocflag.pc b/tests/lib1/nocflag.pc new file mode 100644 index 00000000000..0a3d7524cf4 --- /dev/null +++ b/tests/lib1/nocflag.pc @@ -0,0 +1,9 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo diff --git a/tests/lib1/nolib.pc b/tests/lib1/nolib.pc new file mode 100644 index 00000000000..b47e30c27a8 --- /dev/null +++ b/tests/lib1/nolib.pc @@ -0,0 +1,9 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file +Version: 1.2.3 +Cflags: -fPIC -I${includedir}/foo diff --git a/tests/lib1/omg-sysroot-uninstalled.pc b/tests/lib1/omg-sysroot-uninstalled.pc new file mode 100644 index 00000000000..7e391b951e1 --- /dev/null +++ b/tests/lib1/omg-sysroot-uninstalled.pc @@ -0,0 +1,10 @@ +prefix=${pc_sysrootdir}/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: omg-sysroot +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lomg +Cflags: -fPIC -I${includedir}/omg diff --git a/tests/lib1/omg-uninstalled.pc b/tests/lib1/omg-uninstalled.pc new file mode 100644 index 00000000000..17d8078aa68 --- /dev/null +++ b/tests/lib1/omg-uninstalled.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: omg +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lomg +Cflags: -fPIC -I${includedir}/omg diff --git a/tests/lib1/orphaned-requires-private.pc b/tests/lib1/orphaned-requires-private.pc new file mode 100644 index 00000000000..c62bab4459e --- /dev/null +++ b/tests/lib1/orphaned-requires-private.pc @@ -0,0 +1,8 @@ +Name: orphaned-requires-private +Version: 1 +Description: A testing module for pkgconf +URL: http://www.pkgconf.org/ +Requires: foo +Requires.private: orphaned-foo +Cflags: -DUNREACHABLE +Libs: diff --git a/tests/lib1/paren-quoting.pc b/tests/lib1/paren-quoting.pc new file mode 100644 index 00000000000..08ed49d0258 --- /dev/null +++ b/tests/lib1/paren-quoting.pc @@ -0,0 +1,5 @@ +Name: paren-quoting +Description: Another pkg-config test +Version: 1.3 +Libs: -L$(libdir) -ltilde +Cflags: -I$(includedir) diff --git a/tests/lib1/pcfiledir.pc b/tests/lib1/pcfiledir.pc new file mode 100644 index 00000000000..fa6ada923c4 --- /dev/null +++ b/tests/lib1/pcfiledir.pc @@ -0,0 +1,8 @@ +prefix=${pcfiledir} +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file with a ${pcfiledir} +Version: 1.2.3 diff --git a/tests/lib1/prefix-foo1.pc b/tests/lib1/prefix-foo1.pc new file mode 100644 index 00000000000..7a6e9008964 --- /dev/null +++ b/tests/lib1/prefix-foo1.pc @@ -0,0 +1,12 @@ +prefix=/test/bar +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: prefix-foo1 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo1 +Cflags: -fPIC -I${includedir}/foo -DBAR +Cflags.private: -DFOO_STATIC + diff --git a/tests/lib1/prefix-foo2.pc b/tests/lib1/prefix-foo2.pc new file mode 100644 index 00000000000..0546975e230 --- /dev/null +++ b/tests/lib1/prefix-foo2.pc @@ -0,0 +1,12 @@ +prefix=/test/bar +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: prefix-foo2 +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo2 +Cflags: -fPIC -I${includedir}/foo -DFOO +Cflags.private: -DFOO_STATIC + diff --git a/tests/lib1/private-libs-duplication.pc b/tests/lib1/private-libs-duplication.pc new file mode 100644 index 00000000000..7f0f5734360 --- /dev/null +++ b/tests/lib1/private-libs-duplication.pc @@ -0,0 +1,7 @@ +Name: private-libs-duplication +Description: test +Version: 1.0 +Requires: baz bar +Libs: -lprivate +Libs.private: -lfoo + diff --git a/tests/lib1/provides-request-simple.pc b/tests/lib1/provides-request-simple.pc new file mode 100644 index 00000000000..7e12c2d40dd --- /dev/null +++ b/tests/lib1/provides-request-simple.pc @@ -0,0 +1,6 @@ +Name: provides-request-simple +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lfoo +Cflags: -I/usr/include/foo +Requires: provides-test-foo diff --git a/tests/lib1/provides.pc b/tests/lib1/provides.pc new file mode 100644 index 00000000000..0480c7375c8 --- /dev/null +++ b/tests/lib1/provides.pc @@ -0,0 +1,6 @@ +Name: provides +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lfoo +Cflags: -I/usr/include/foo +Provides: provides-test-foo = 1.0.0, provides-test-bar > 1.1.0, provides-test-baz >= 1.1.0, provides-test-quux < 1.2.0, provides-test-moo <= 1.2.0, provides-test-meow != 1.3.0 diff --git a/tests/lib1/quotes.pc b/tests/lib1/quotes.pc new file mode 100644 index 00000000000..3213fbe5a1e --- /dev/null +++ b/tests/lib1/quotes.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: quotes +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -DQUOTED=\"bla\" -DA=\"escaped\ string\'\ \literal\" -DB="\1\$" -DC='bla' diff --git a/tests/lib1/requires-internal-2.pc b/tests/lib1/requires-internal-2.pc new file mode 100644 index 00000000000..84677205cf3 --- /dev/null +++ b/tests/lib1/requires-internal-2.pc @@ -0,0 +1,9 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: requires-internal +Description: A testing pkg-config file +Version: 1.2.3 +Requires.internal: cflags-internal diff --git a/tests/lib1/requires-internal-collision.pc b/tests/lib1/requires-internal-collision.pc new file mode 100644 index 00000000000..2b257cf4f9e --- /dev/null +++ b/tests/lib1/requires-internal-collision.pc @@ -0,0 +1,10 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: requires-internal +Description: A testing pkg-config file +Version: 1.2.3 +Requires.internal: cflags-internal +Requires.private: cflags-internal diff --git a/tests/lib1/requires-internal-missing.pc b/tests/lib1/requires-internal-missing.pc new file mode 100644 index 00000000000..8c55a1ddfa6 --- /dev/null +++ b/tests/lib1/requires-internal-missing.pc @@ -0,0 +1,10 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: requires-internal +Description: A testing pkg-config file +Version: 1.2.3 +Requires.internal: static-libs, missing +Requires.private: foo diff --git a/tests/lib1/requires-internal.pc b/tests/lib1/requires-internal.pc new file mode 100644 index 00000000000..b7d5a05d93b --- /dev/null +++ b/tests/lib1/requires-internal.pc @@ -0,0 +1,10 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: requires-internal +Description: A testing pkg-config file +Version: 1.2.3 +Requires.internal: static-libs +Requires.private: foo diff --git a/tests/lib1/spaces-in-paths.pc b/tests/lib1/spaces-in-paths.pc new file mode 100644 index 00000000000..490624e84c6 --- /dev/null +++ b/tests/lib1/spaces-in-paths.pc @@ -0,0 +1,7 @@ +prefix=/test\ with\ spaces +includedir=${prefix}/include + +Name: spaces-in-paths +Version: 1 +Description: test package for properly expanding spaces in variables +Cflags: -I${includedir} -I${includedir}/subdir diff --git a/tests/lib1/static-archive-libs.pc b/tests/lib1/static-archive-libs.pc new file mode 100644 index 00000000000..95f7bfba92a --- /dev/null +++ b/tests/lib1/static-archive-libs.pc @@ -0,0 +1,10 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: static-archive-libs +Description: A testing pkg-config file +Version: 1.2.3 +Libs: /libfoo.a -pthread +Cflags: -I${includedir}/foo diff --git a/tests/lib1/static-libs.pc b/tests/lib1/static-libs.pc new file mode 100644 index 00000000000..075b331ff19 --- /dev/null +++ b/tests/lib1/static-libs.pc @@ -0,0 +1,12 @@ +prefix=/test/local +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: static-libs +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -lbar +Libs.private: -lbar-private +Cflags: -I${includedir}/foo +Requires.private: foo diff --git a/tests/lib1/sysroot-dir-2.pc b/tests/lib1/sysroot-dir-2.pc new file mode 100644 index 00000000000..31f3ffdb067 --- /dev/null +++ b/tests/lib1/sysroot-dir-2.pc @@ -0,0 +1,7 @@ +prefix=${pc_sysrootdir}/usr + +Name: sysroot-dir +Description: Package for testing whether sysroot dirs are repeated or not +Version: 1 +CFlags: -I${prefix}/include +Libs: -L${prefix}/lib -lfoo diff --git a/tests/lib1/sysroot-dir-3.pc b/tests/lib1/sysroot-dir-3.pc new file mode 100644 index 00000000000..8c53a453a44 --- /dev/null +++ b/tests/lib1/sysroot-dir-3.pc @@ -0,0 +1,9 @@ +prefix=/sysroot/usr +includedir=${prefix}/include +libdir=${prefix}/lib + +Name: sysroot-dir +Description: Package for testing whether sysroot dirs are repeated or not +Version: 1 +CFlags: -I${includedir} +Libs: -L${libdir} -lfoo diff --git a/tests/lib1/sysroot-dir-4.pc b/tests/lib1/sysroot-dir-4.pc new file mode 100644 index 00000000000..524dc7c17cf --- /dev/null +++ b/tests/lib1/sysroot-dir-4.pc @@ -0,0 +1,9 @@ +prefix=${pc_sysrootdir}/usr +includedir=${prefix}/include +libdir=${prefix}/lib + +Name: sysroot-dir +Description: Package for testing whether sysroot dirs are repeated or not +Version: 1 +CFlags: -I${includedir} +Libs: -L${libdir} -lfoo diff --git a/tests/lib1/sysroot-dir-5.pc b/tests/lib1/sysroot-dir-5.pc new file mode 100644 index 00000000000..8c53a453a44 --- /dev/null +++ b/tests/lib1/sysroot-dir-5.pc @@ -0,0 +1,9 @@ +prefix=/sysroot/usr +includedir=${prefix}/include +libdir=${prefix}/lib + +Name: sysroot-dir +Description: Package for testing whether sysroot dirs are repeated or not +Version: 1 +CFlags: -I${includedir} +Libs: -L${libdir} -lfoo diff --git a/tests/lib1/sysroot-dir.pc b/tests/lib1/sysroot-dir.pc new file mode 100644 index 00000000000..8334c501972 --- /dev/null +++ b/tests/lib1/sysroot-dir.pc @@ -0,0 +1,5 @@ +Name: sysroot-dir +Description: Package for testing whether sysroot dirs are repeated or not +Version: 1 +CFlags: -I/sysroot/include +Libs: -L/sysroot/lib -lfoo diff --git a/tests/lib1/tilde-quoting.pc b/tests/lib1/tilde-quoting.pc new file mode 100644 index 00000000000..4d37978c748 --- /dev/null +++ b/tests/lib1/tilde-quoting.pc @@ -0,0 +1,5 @@ +Name: tilde +Description: Another pkg-config test +Version: 1.3 +Libs: -L~ -ltilde +Cflags: -I~ diff --git a/tests/lib1/tilde.pc b/tests/lib1/tilde.pc new file mode 100644 index 00000000000..a4002ecd29e --- /dev/null +++ b/tests/lib1/tilde.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: Tilde version test +Description: Test package for checking rpmvercmp ~ handling +Version: 1.0.0~rc1 +Requires: +Libs: -L${libdir} -ltilde +Cflags: -I${includedir} diff --git a/tests/lib1/truncated.pc b/tests/lib1/truncated.pc new file mode 100644 index 00000000000..4977bc62c0e --- /dev/null +++ b/tests/lib1/truncated.pc @@ -0,0 +1 @@ +~ \ No newline at end of file diff --git a/tests/lib1/tuple-quoting.pc b/tests/lib1/tuple-quoting.pc new file mode 100644 index 00000000000..666ba4a7a46 --- /dev/null +++ b/tests/lib1/tuple-quoting.pc @@ -0,0 +1,7 @@ +prefix="/test" +libdir=${prefix}/lib + +Name: tuple-quoting +Description: Another pkg-config test +Version: 1.3 +Libs: -L${libdir} -lfoo diff --git a/tests/lib1/typelibdir.pc b/tests/lib1/typelibdir.pc new file mode 100644 index 00000000000..16e46c4729f --- /dev/null +++ b/tests/lib1/typelibdir.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include +typelibdir=${libdir}/typelibdir + +Name: test +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -llib-1 +Cflags: diff --git a/tests/lib1/unavailable-provider.pc b/tests/lib1/unavailable-provider.pc new file mode 100644 index 00000000000..a8eed6648b5 --- /dev/null +++ b/tests/lib1/unavailable-provider.pc @@ -0,0 +1,11 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: unavailable_provider +Description: Provides an otherwise unavailable package +Version: 1.2.3 +Provides: unavailable = 1.2.3 +Libs: -lunavailable +Cflags: diff --git a/tests/lib1/utf8.pc b/tests/lib1/utf8.pc new file mode 100644 index 00000000000..6507ec0f25b --- /dev/null +++ b/tests/lib1/utf8.pc @@ -0,0 +1,10 @@ +prefix=/tëst +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: utf8 +Description: Library installed in a prefix with UTF-8 +Version: 0 +Libs: -L${libdir} -lutf8 +Cflags: -I${includedir} diff --git a/tests/lib1/variable-whitespace.pc b/tests/lib1/variable-whitespace.pc new file mode 100644 index 00000000000..8d8eab22cde --- /dev/null +++ b/tests/lib1/variable-whitespace.pc @@ -0,0 +1,11 @@ + +prefix=/test +exec_prefix= ${prefix} +libdir= ${prefix}/lib64 +includedir=${prefix}/include + +Name: variable-whitespace +Description: A test for variable whitespace +Version: 2.12.3 +Libs: -lvariable-whitespace -L${libdir} +Cflags: -I${includedir} diff --git a/tests/lib2/foo.pc b/tests/lib2/foo.pc new file mode 100644 index 00000000000..44588f24352 --- /dev/null +++ b/tests/lib2/foo.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: foo +Description: A testing pkg-config file +Version: 1.2.3 +Libs: -L${libdir} -lfoo +Cflags: -fPIC -I${includedir}/foo diff --git a/tests/lib3/bar.pc b/tests/lib3/bar.pc new file mode 100644 index 00000000000..9157d06b5fd --- /dev/null +++ b/tests/lib3/bar.pc @@ -0,0 +1,10 @@ +prefix=/test +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: bar +Description: Another pkg-config test +Version: 1.3 +Libs: -L${libdir} -lbar +Requires: foo diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 00000000000..56406c3bb0c --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,23 @@ +configure_file(input: 'Kyuafile.in', output: 'Kyuafile', configuration: cdata) +configure_file(input: 'test_env.sh.in', output: 'test_env.sh', configuration: cdata) + + +tests = [ + 'basic', + 'builtins', + 'conflicts', + 'framework', + 'parser', + 'provides', + 'regress', + 'requires', + 'symlink', + 'sysroot', + 'version', +] + + +# yuck +foreach test : tests + test_file = configure_file(input: test + '.sh', output: test, copy: true) +endforeach diff --git a/tests/parser.sh b/tests/parser.sh new file mode 100755 index 00000000000..9d9f9c6ff4b --- /dev/null +++ b/tests/parser.sh @@ -0,0 +1,372 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + comments \ + comments_in_fields \ + dos \ + no_trailing_newline \ + argv_parse \ + bad_option \ + argv_parse_3 \ + tilde_quoting \ + paren_quoting \ + multiline_field \ + multiline_bogus_header \ + escaped_backslash \ + flag_order_1 \ + flag_order_2 \ + flag_order_3 \ + flag_order_4 \ + quoted \ + variable_whitespace \ + fragment_escaping_1 \ + fragment_escaping_2 \ + fragment_escaping_3 \ + fragment_quoting \ + fragment_quoting_2 \ + fragment_quoting_3 \ + fragment_quoting_5 \ + fragment_quoting_7 \ + fragment_comment \ + msvc_fragment_quoting \ + msvc_fragment_render_cflags \ + tuple_dequote \ + version_with_whitespace \ + version_with_whitespace_2 \ + version_with_whitespace_diagnostic \ + fragment_groups \ + fragment_groups_composite \ + fragment_tree \ + truncated \ + c_comment + +comments_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lfoo\n" \ + pkgconf --libs comments +} + +comments_in_fields_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lfoo\n" \ + pkgconf --libs comments-in-fields +} + +dos_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib/dos-lineendings -ldos-lineendings\n" \ + pkgconf --libs dos-lineendings +} + +no_trailing_newline_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-I/test/include/no-trailing-newline\n" \ + pkgconf --cflags no-trailing-newline +} + +argv_parse_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-llib-3 -llib-1 -llib-2 -lpthread\n" \ + pkgconf --libs argv-parse +} + +bad_option_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -e ignore \ + -s eq:1 \ + pkgconf --exists -foo +} + +argv_parse_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-llib-1 -pthread /test/lib/lib2.so\n" \ + pkgconf --libs argv-parse-3 +} + +tilde_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L~ -ltilde\n" \ + pkgconf --libs tilde-quoting + atf_check \ + -o inline:"-I~\n" \ + pkgconf --cflags tilde-quoting +} + +paren_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L\$(libdir) -ltilde\n" \ + pkgconf --libs paren-quoting +} + +multiline_field_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -e ignore \ + -o match:"multiline description" \ + pkgconf --list-all +} + +multiline_bogus_header_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s eq:0 \ + pkgconf --exists multiline-bogus +} + +escaped_backslash_body() +{ + atf_check \ + -e ignore \ + -o inline:"-IC:\\\\\\\\A\n" \ + pkgconf --with-path=${selfdir}/lib1 --cflags escaped-backslash +} + +quoted_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-DQUOTED=\\\"bla\\\" -DA=\\\"escaped\\ string\\\'\\ literal\\\" -DB=\\\\\\1\$ -DC=bla\n" \ + pkgconf --cflags quotes +} + +flag_order_1_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -Bdynamic -lfoo -Bstatic -lbar\n" \ + pkgconf --libs flag-order-1 +} + +flag_order_2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -Bdynamic -lfoo -Bstatic -lbar -lfoo\n" \ + pkgconf --libs flag-order-1 foo +} + +flag_order_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -Wl,--start-group -lfoo -lbar -Wl,--end-group\n" \ + pkgconf --libs flag-order-3 +} + +flag_order_4_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -Wl,--start-group -lfoo -lbar -Wl,--end-group -lfoo\n" \ + pkgconf --libs flag-order-3 foo +} + +variable_whitespace_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-I/test/include\n" \ + pkgconf --cflags variable-whitespace +} + +fragment_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -DQUOTED=\\\"/test/share/doc\\\"\n" \ + pkgconf --cflags fragment-quoting +} + +fragment_quoting_2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -DQUOTED=/test/share/doc\n" \ + pkgconf --cflags fragment-quoting-2 +} + +fragment_quoting_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -DQUOTED=\\\"/test/share/doc\\\"\n" \ + pkgconf --cflags fragment-quoting-3 +} + +fragment_quoting_5_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -DQUOTED=/test/share/doc\n" \ + pkgconf --cflags fragment-quoting-5 +} + +fragment_quoting_7_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\\ world\n" \ + pkgconf --cflags fragment-quoting-7 +} + +fragment_escaping_1_body() +{ + atf_check \ + -o inline:"-IC:\\\\\\\\D\\ E\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-1 +} + +fragment_escaping_2_body() +{ + atf_check \ + -o inline:"-IC:\\\\\\\\D\\ E\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-2 +} + +fragment_escaping_3_body() +{ + atf_check \ + -o inline:"-IC:\\\\\\\\D\\ E\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags fragment-escaping-3 +} + +fragment_quoting_7a_body() +{ + set -x + + test_cflags=$(pkgconf --with-path=${selfdir}/lib1 --cflags fragment-quoting-7) + echo $test_cflags +# test_cflags='-Dhello=10 -Dworld=+32 -DDEFINED_FROM_PKG_CONFIG=hello\\ world' + + cat > test.c <<- __TESTCASE_END__ + int main(int argc, char *argv[]) { return DEFINED_FROM_PKG_CONFIG; } + __TESTCASE_END__ + cc -o test-fragment-quoting-7 ${test_cflags} ./test.c + atf_check -e 42 ./test-fragment-quoting-7 + rm -f test.c test-fragment-quoting-7 + + set +x +} + + +fragment_comment_body() +{ + atf_check \ + -o inline:'kuku=\#ttt\n' \ + pkgconf --with-path="${selfdir}/lib1" --cflags fragment-comment +} + +msvc_fragment_quoting_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:'/libpath:"C:\D E" E.lib \n' \ + pkgconf --libs --msvc-syntax fragment-escaping-1 +} + +msvc_fragment_render_cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:'/I/test/include/foo /DFOO_STATIC \n' \ + pkgconf --cflags --static --msvc-syntax foo +} + +tuple_dequote_body() +{ + atf_check \ + -o inline:'-L/test/lib -lfoo\n' \ + pkgconf --with-path="${selfdir}/lib1" --libs tuple-quoting +} + +version_with_whitespace_body() +{ + atf_check \ + -o inline:'3.922\n' \ + pkgconf --with-path="${selfdir}/lib1" --modversion malformed-version +} + +version_with_whitespace_2_body() +{ + atf_check \ + -o inline:'malformed-version = 3.922\n' \ + pkgconf --with-path="${selfdir}/lib1" --print-provides malformed-version +} + +version_with_whitespace_diagnostic_body() +{ + atf_check \ + -o match:warning \ + pkgconf --with-path="${selfdir}/lib1" --validate malformed-version +} + +fragment_groups_body() +{ + atf_check \ + -o inline:'-Wl,--start-group -la -lb -Wl,--end-group -nodefaultlibs -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections\n' \ + pkgconf --with-path="${selfdir}/lib1" --libs fragment-groups +} + +fragment_groups_composite_body() +{ + atf_check \ + -o inline:'-Wl,--start-group -la -lb -Wl,--end-group -nodefaultlibs -Wl,--start-group -la -lgcc -Wl,--end-group -Wl,--gc-sections\n' \ + pkgconf --with-path="${selfdir}/lib1" --libs fragment-groups-2 +} + +truncated_body() +{ + atf_check \ + -o match:warning -s exit:1 \ + pkgconf --with-path="${selfdir}/lib1" --validate truncated +} + +c_comment_body() +{ + atf_check \ + -o match:warning \ + pkgconf --with-path="${selfdir}/lib1" --validate c-comment +} + +fragment_tree_body() +{ + atf_check \ + -o inline:"'-Wl,--start-group' [untyped] + '-la' [type l] + '-lb' [type l] + '-Wl,--end-group' [untyped] + +'-nodefaultlibs' [untyped] +'-Wl,--start-group' [untyped] + '-la' [type l] + '-lgcc' [type l] + '-Wl,--end-group' [untyped] + +'-Wl,--gc-sections' [untyped] + +" \ + pkgconf --with-path="${selfdir}/lib1" --fragment-tree fragment-groups-2 +} + diff --git a/tests/provides.sh b/tests/provides.sh new file mode 100755 index 00000000000..b699dbf563f --- /dev/null +++ b/tests/provides.sh @@ -0,0 +1,310 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + simple \ + foo \ + bar \ + baz \ + quux \ + moo \ + meow \ + indirect_dependency_node + +simple_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" +OUTPUT="provides-test-foo = 1.0.0 +provides-test-bar > 1.1.0 +provides-test-baz >= 1.1.0 +provides-test-quux < 1.2.0 +provides-test-moo <= 1.2.0 +provides-test-meow != 1.3.0 +provides = 1.2.3 +" + atf_check \ + -o inline:"${OUTPUT}" \ + pkgconf --print-provides provides + atf_check \ + -o inline:"-lfoo\n" \ + pkgconf --libs provides-request-simple + atf_check \ + -e ignore \ + -s exit:1 \ + pkgconf --no-provides --libs provides-request-simple +} + +foo_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-foo + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-foo = 1.0.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-foo >= 1.0.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-foo <= 1.0.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-foo != 1.0.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-foo > 1.0.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-foo < 1.0.0' +} + +bar_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-bar + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar = 1.1.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar >= 1.1.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar <= 1.1.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar != 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-bar != 1.1.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar > 1.1.1' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-bar <= 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-bar <= 1.2.0' +} + +baz_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-baz + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz = 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz >= 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz <= 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-baz != 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz != 1.0.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz > 1.1.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz > 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-baz < 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-baz < 1.2.0' +} + +quux_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-quux + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux = 1.1.9' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux >= 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux >= 1.1.9' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux >= 1.2.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux <= 1.2.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux <= 1.1.9' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux != 1.2.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux != 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux != 1.0.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux > 1.1.9' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux > 1.2.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-quux < 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-quux > 1.2.0' +} + +moo_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-moo + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo = 1.2.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo >= 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo >= 1.2.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-moo >= 1.2.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo <= 1.2.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-moo != 1.1.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-moo != 1.0.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo > 1.1.9' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-moo > 1.2.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo < 1.1.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-moo < 1.2.0' +} + +meow_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o ignore \ + pkgconf --libs provides-test-meow + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-meow = 1.3.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-meow != 1.3.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-meow > 1.2.9' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-meow < 1.3.1' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-meow < 1.3.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-meow > 1.3.0' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-meow >= 1.3.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-meow >= 1.3.1' + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --libs 'provides-test-meow <= 1.3.0' + atf_check \ + -o ignore \ + pkgconf --libs 'provides-test-meow < 1.2.9' +} + +indirect_dependency_node_body() +{ + atf_check \ + -o inline:'1.2.3\n' \ + pkgconf --with-path="${selfdir}/lib1" --modversion 'provides-test-meow' + atf_check \ + -s exit:1 \ + -e ignore \ + pkgconf --with-path="${selfdir}/lib1" --modversion 'provides-test-meow = 1.3.0' +} diff --git a/tests/regress.sh b/tests/regress.sh new file mode 100755 index 00000000000..b264f3593f9 --- /dev/null +++ b/tests/regress.sh @@ -0,0 +1,354 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + case_sensitivity \ + depgraph_break_1 \ + depgraph_break_2 \ + depgraph_break_3 \ + define_variable \ + define_variable_override \ + variable \ + keep_system_libs \ + libs \ + libs_only \ + libs_never_mergeback \ + cflags_only \ + cflags_never_mergeback \ + incomplete_libs \ + incomplete_cflags \ + isystem_munge_order \ + isystem_munge_sysroot \ + idirafter_munge_order \ + idirafter_munge_sysroot \ + idirafter_ordering \ + modversion_common_prefix \ + modversion_fullpath \ + modversion_provides \ + modversion_uninstalled \ + modversion_one_word_expression \ + modversion_two_word_expression \ + modversion_three_word_expression \ + modversion_one_word_expression_no_space \ + modversion_one_word_expression_no_space_zero \ + pcpath \ + virtual_variable \ + fragment_collision \ + malformed_1 \ + malformed_quoting \ + explicit_sysroot \ + empty_tuple \ + solver_requires_private_debounce \ + billion_laughs \ + define_prefix_child_prefix_1 \ + define_prefix_child_prefix_1_env + +# sysroot_munge \ + +case_sensitivity_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"3\n" \ + pkgconf --variable=foo case-sensitivity + atf_check \ + -o inline:"4\n" \ + pkgconf --variable=Foo case-sensitivity +} + +depgraph_break_1_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -s exit:1 -e ignore \ + pkgconf --exists --print-errors 'foo > 0.6.0 foo < 0.8.0' +} + +depgraph_break_2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -s exit:1 -e ignore \ + pkgconf --exists --print-errors 'nonexisting foo <= 3' +} + +depgraph_break_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -s exit:1 -e ignore \ + pkgconf --exists --print-errors 'depgraph-break' +} + +define_variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -o inline:"\\\${libdir}/typelibdir\n" \ + pkgconf --variable=typelibdir --define-variable='libdir=\${libdir}' typelibdir +} + +define_variable_override_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check -o inline:"/test\n" \ + pkgconf --variable=prefix --define-variable='prefix=/test' typelibdir +} + +variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"/test/include\n" \ + pkgconf --variable=includedir foo +} + +keep_system_libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + eval export "$LIBRARY_PATH_ENV"="/test/local/lib" + atf_check \ + -o inline:"\n" \ + pkgconf --libs-only-L cflags-libs-only + + atf_check \ + -o inline:"-L/test/local/lib\n" \ + pkgconf --libs-only-L --keep-system-libs cflags-libs-only +} + +libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/local/lib -lfoo\n" \ + pkgconf --libs cflags-libs-only +} + +libs_only_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/local/lib -lfoo\n" \ + pkgconf --libs-only-L --libs-only-l cflags-libs-only +} + +libs_never_mergeback_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/bar/lib -lfoo1\n" \ + pkgconf --libs prefix-foo1 + atf_check \ + -o inline:"-L/test/bar/lib -lfoo1 -lfoo2\n" \ + pkgconf --libs prefix-foo1 prefix-foo2 +} + +cflags_only_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-I/test/local/include/foo\n" \ + pkgconf --cflags-only-I --cflags-only-other cflags-libs-only +} + +cflags_never_mergeback_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-I/test/bar/include/foo -DBAR -fPIC -DFOO\n" \ + pkgconf --cflags prefix-foo1 prefix-foo2 +} + +incomplete_libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"\n" \ + pkgconf --libs incomplete +} + +incomplete_cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"\n" \ + pkgconf --cflags incomplete +} + +isystem_munge_order_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-isystem /opt/bad/include -isystem /opt/bad2/include\n" \ + pkgconf --cflags isystem +} + +isystem_munge_sysroot_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}" + atf_check \ + -o match:"-isystem ${selfdir}/opt/bad/include" \ + pkgconf --cflags isystem +} + +idirafter_munge_order_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-idirafter /opt/bad/include -idirafter /opt/bad2/include\n" \ + pkgconf --cflags idirafter +} + +idirafter_munge_sysroot_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}" + atf_check \ + -o match:"-idirafter ${selfdir}/opt/bad/include" \ + pkgconf --cflags idirafter +} + +idirafter_ordering_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-I/opt/bad/include1 -idirafter -I/opt/bad/include2 -I/opt/bad/include3\n" \ + pkgconf --cflags idirafter-ordering +} + +pcpath_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib2" + atf_check \ + -o inline:"-fPIC -I/test/include/foo\n" \ + pkgconf --cflags ${selfdir}/lib3/bar.pc +} + +sysroot_munge_body() +{ + sed "s|/sysroot/|${selfdir}/|g" ${selfdir}/lib1/sysroot-dir.pc > ${selfdir}/lib1/sysroot-dir-selfdir.pc + export PKG_CONFIG_PATH="${selfdir}/lib1" PKG_CONFIG_SYSROOT_DIR="${selfdir}" + atf_check \ + -o inline:"-L${selfdir}/lib -lfoo\n" \ + pkgconf --libs sysroot-dir-selfdir +} + +virtual_variable_body() +{ + atf_check -s exit:0 \ + pkgconf --exists pkg-config + atf_check -s exit:0 \ + pkgconf --exists pkgconf + + atf_check -o inline:"${pcpath}\n" \ + pkgconf --variable=pc_path pkg-config + atf_check -o inline:"${pcpath}\n" \ + pkgconf --variable=pc_path pkgconf +} + +fragment_collision_body() +{ + atf_check -o inline:"-D_BAZ -D_BAR -D_FOO -D_THREAD_SAFE -pthread\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags fragment-collision +} + +malformed_1_body() +{ + atf_check -s exit:1 -o ignore \ + pkgconf --validate --with-path="${selfdir}/lib1" malformed-1 +} + +malformed_quoting_body() +{ + atf_check -s exit:0 -o ignore \ + pkgconf --validate --with-path="${selfdir}/lib1" malformed-quoting +} + +explicit_sysroot_body() +{ + export PKG_CONFIG_SYSROOT_DIR=${selfdir} + atf_check -o inline:"${selfdir}/usr/share/test\n" \ + pkgconf --with-path="${selfdir}/lib1" --variable=pkgdatadir explicit-sysroot +} + +empty_tuple_body() +{ + atf_check -o inline:"\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags empty-tuple +} + +solver_requires_private_debounce_body() +{ + atf_check -o inline:"-I/metapackage-1 -I/metapackage-2 -lmetapackage-1 -lmetapackage-2\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags --libs metapackage +} + +billion_laughs_body() +{ + atf_check -o inline:"warning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\nwarning: truncating very long variable to 64KB\n" \ + pkgconf --with-path="${selfdir}/lib1" --validate billion-laughs +} + +modversion_common_prefix_body() +{ + atf_check -o inline:"foo: 1.2.3\nfoobar: 3.2.1\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion --verbose foo foobar +} + +modversion_fullpath_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --modversion "${selfdir}/lib1/foo.pc" +} + +modversion_provides_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion unavailable +} + +modversion_uninstalled_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion omg +} + +modversion_one_word_expression_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion "foo > 1.0" +} + +modversion_two_word_expression_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion foo "> 1.0" +} + +modversion_three_word_expression_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion foo ">" 1.0 +} + +modversion_one_word_expression_no_space_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion "foo >1.0" +} + +modversion_one_word_expression_no_space_zero_body() +{ + atf_check -o inline:"1.2.3\n" \ + pkgconf --with-path="${selfdir}/lib1" --modversion "foo >0.5" +} + +define_prefix_child_prefix_1_body() +{ + atf_check -o inline:"-I${selfdir}/lib1/include/child-prefix-1 -L${selfdir}/lib1/lib64 -lchild-prefix-1\n" \ + pkgconf --with-path="${selfdir}/lib1/child-prefix/pkgconfig" --define-prefix --cflags --libs child-prefix-1 +} + +define_prefix_child_prefix_1_env_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1/child-prefix/pkgconfig" + export PKG_CONFIG_RELOCATE_PATHS=1 + atf_check -o inline:"-I${selfdir}/lib1/include/child-prefix-1 -L${selfdir}/lib1/lib64 -lchild-prefix-1\n" \ + pkgconf --cflags --libs child-prefix-1 +} diff --git a/tests/requires.sh b/tests/requires.sh new file mode 100755 index 00000000000..7a95a66cd23 --- /dev/null +++ b/tests/requires.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + libs \ + libs_cflags \ + libs_static \ + libs_static_pure \ + cflags_libs_private \ + argv_parse2 \ + static_cflags \ + private_duplication \ + private_duplication_digraph \ + foo_bar \ + bar_foo \ + foo_metapackage_3 \ + libs_static2 \ + missing \ + requires_internal \ + requires_internal_missing \ + requires_internal_collision \ + orphaned_requires_private + +libs_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lbar -lfoo\n" \ + pkgconf --libs bar +} + +libs_cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -L/test/lib -lbaz\n" \ + pkgconf --libs --cflags baz +} + +libs_static_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lbaz -L/test/lib -lzee -lfoo\n" \ + pkgconf --static --libs baz +} + +libs_static_pure_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-L/test/lib -lbaz -lfoo\n" \ + pkgconf --static --pure --libs baz +} + +argv_parse2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-llib-1 -pthread /test/lib/lib2.so\n" \ + pkgconf --static --libs argv-parse-2 +} + +static_cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-fPIC -I/test/include/foo -DFOO_STATIC\n" \ + pkgconf --static --cflags baz +} + +private_duplication_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lprivate -lbaz -lzee -lbar -lfoo\n" \ + pkgconf --static --libs-only-l private-libs-duplication +} + +private_duplication_digraph_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o 'match:"user:request" -> "private-libs-duplication"' \ + -o 'match:"private-libs-duplication" -> "bar"' \ + -o 'match:"private-libs-duplication" -> "baz"' \ + -o 'match:"bar" -> "foo"' \ + -o 'match:"baz" -> "foo"' \ + pkgconf --static --libs-only-l private-libs-duplication --digraph +} + +bar_foo_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lbar -lfoo\n" \ + pkgconf --static --libs-only-l bar foo +} + +foo_bar_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lbar -lfoo\n" \ + pkgconf --static --libs-only-l foo bar +} + +foo_metapackage_3_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lbar -lfoo\n" \ + pkgconf --static --libs-only-l foo metapackage-3 +} + +libs_static2_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -o inline:"-lbar -lbar-private -L/test/lib -lfoo\n" \ + pkgconf --static --libs static-libs +} + +missing_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --cflags missing-require +} + +requires_internal_body() +{ + atf_check \ + -o inline:"-lbar -lbar-private -L/test/lib -lfoo\n" \ + pkgconf --with-path="${selfdir}/lib1" --static --libs requires-internal +} + +requires_internal_missing_body() +{ + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --with-path="${selfdir}/lib1" --static --libs requires-internal-missing +} + +requires_internal_collision_body() +{ + atf_check \ + -o inline:"-I/test/local/include/foo\n" \ + pkgconf --with-path="${selfdir}/lib1" --cflags requires-internal-collision +} + +orphaned_requires_private_body() +{ + atf_check \ + -s exit:1 \ + -e ignore \ + -o ignore \ + pkgconf --with-path="${selfdir}/lib1" --cflags --libs orphaned-requires-private +} + +cflags_libs_private_body() +{ + atf_check \ + -o inline:"\n" \ + pkgconf --with-path="${selfdir}/lib1" --libs cflags-libs-private-a + + atf_check \ + -o inline:"-lc\n" \ + pkgconf --with-path="${selfdir}/lib1" --static --libs cflags-libs-private-a +} diff --git a/tests/symlink.sh b/tests/symlink.sh new file mode 100755 index 00000000000..7f49655755e --- /dev/null +++ b/tests/symlink.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + pcfiledir_symlink_absolute \ + pcfiledir_symlink_relative + +# - We need to create a temporary subtree, since symlinks are not preserved +# in "make dist". +# - ${srcdir} is relative and since we need to compare paths, we would have +# to portably canonicalize it again, which is hard. Instead, just keep +# the whole thing nested. +pcfiledir_symlink_absolute_body() +{ + mkdir -p tmp/child + cp -f "${selfdir}/lib1/pcfiledir.pc" tmp/child/ + ln -f -s "${PWD}/tmp/child/pcfiledir.pc" tmp/pcfiledir.pc # absolute + ln -f -s tmp/pcfiledir.pc pcfiledir.pc + + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir.pc + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix tmp/pcfiledir.pc + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix tmp/child/pcfiledir.pc + + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/pcfiledir.pc" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/tmp/pcfiledir.pc" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/tmp/child/pcfiledir.pc" + + export PKG_CONFIG_PATH="." + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + + export PKG_CONFIG_PATH="tmp" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}/tmp" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + + export PKG_CONFIG_PATH="tmp/child" + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}/tmp/child" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir +} + +pcfiledir_symlink_relative_body() +{ + mkdir -p tmp/child + cp -f "${selfdir}/lib1/pcfiledir.pc" tmp/child/ + ln -f -s child/pcfiledir.pc tmp/pcfiledir.pc # relative + ln -f -s tmp/pcfiledir.pc pcfiledir.pc + + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix pcfiledir.pc + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix tmp/pcfiledir.pc + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix tmp/child/pcfiledir.pc + + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/pcfiledir.pc" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/tmp/pcfiledir.pc" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix "${PWD}/tmp/child/pcfiledir.pc" + + export PKG_CONFIG_PATH="." + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + + export PKG_CONFIG_PATH="tmp" + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}/tmp" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + + export PKG_CONFIG_PATH="tmp/child" + atf_check \ + -o inline:"tmp/child\n" \ + pkgconf --variable=prefix pcfiledir + export PKG_CONFIG_PATH="${PWD}/tmp/child" + atf_check \ + -o inline:"${PWD}/tmp/child\n" \ + pkgconf --variable=prefix pcfiledir +} diff --git a/tests/sysroot.sh b/tests/sysroot.sh new file mode 100755 index 00000000000..9d61eaea311 --- /dev/null +++ b/tests/sysroot.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + cflags \ + variable \ + do_not_eat_slash \ + do_not_duplicate_sysroot_dir \ + uninstalled \ + uninstalled_pkgconf1 \ + uninstalled_fdo \ + uninstalled_fdo_pc_sysrootdir + +do_not_eat_slash_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/" + atf_check \ + -o inline:"-fPIC -I/test/include/foo\n" \ + pkgconf --cflags baz +} + +cflags_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}" + atf_check \ + -o inline:"-fPIC -I${SYSROOT_DIR}/test/include/foo\n" \ + pkgconf --cflags baz +} + +variable_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}" + atf_check \ + -o inline:"${SYSROOT_DIR}/test\n" \ + pkgconf --variable=prefix foo + atf_check \ + -o inline:"${SYSROOT_DIR}/test/include\n" \ + pkgconf --variable=includedir foo +} + +do_not_duplicate_sysroot_dir_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/sysroot" + + atf_check \ + -o inline:"-I/sysroot/usr/include\n" \ + pkgconf --cflags sysroot-dir-2 + + atf_check \ + -o inline:"-I/sysroot/usr/include\n" \ + pkgconf --cflags sysroot-dir-3 + + atf_check \ + -o inline:"-I/sysroot/usr/include\n" \ + pkgconf --cflags sysroot-dir-5 + + export PKG_CONFIG_SYSROOT_DIR="${SYSROOT_DIR}" + + atf_check \ + -o inline:"-I${SYSROOT_DIR}/usr/include\n" \ + pkgconf --cflags sysroot-dir-4 +} + +uninstalled_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/sysroot" + + atf_check \ + -o inline:"-L/test/lib -lomg\n" \ + pkgconf --libs omg +} + +uninstalled_pkgconf1_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/sysroot" + export PKG_CONFIG_PKGCONF1_SYSROOT_RULES="1" + + atf_check \ + -o inline:"-L/sysroot/test/lib -lomg\n" \ + pkgconf --libs omg +} + +uninstalled_fdo_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/sysroot" + export PKG_CONFIG_FDO_SYSROOT_RULES="1" + + atf_check \ + -o inline:"-L/test/lib -lomg\n" \ + pkgconf --libs omg +} + +uninstalled_fdo_pc_sysrootdir_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + export PKG_CONFIG_SYSROOT_DIR="/sysroot" + export PKG_CONFIG_FDO_SYSROOT_RULES="1" + + atf_check \ + -o inline:"-L/sysroot/test/lib -lomg\n" \ + pkgconf --libs omg-sysroot +} diff --git a/tests/test_env.sh.in b/tests/test_env.sh.in new file mode 100644 index 00000000000..d2b6136f18c --- /dev/null +++ b/tests/test_env.sh.in @@ -0,0 +1,50 @@ +srcdir="$(atf_get_srcdir)" +export PATH="$srcdir/..:${PATH}" + +#--- begin windows kludge --- +# When building with Visual Studio, binaries are in a subdirectory named after the configration... +# and the configuration is not known unless you're in the IDE, or something. +# So just guess. This won't work well if you build more than one configuration. +the_configuration="" +for configuration in Debug Release RelWithDebInfo +do + if test -d "$srcdir/../$configuration" + then + if test "$the_configuration" != "" + then + echo "test_env.sh: FAIL: more than one configuration found" + exit 1 + fi + the_configuration=$configuration + export PATH="$srcdir/../${configuration}:${PATH}" + fi +done +#--- end kludge --- + +selfdir="@abs_top_srcdir@/tests" +LIBRARY_PATH_ENV="LIBRARY_PATH" +PATH_SEP=":" +SYSROOT_DIR="${selfdir}" +case "$(uname -s)" in +Haiku) LIBRARY_PATH_ENV="BELIBRARIES";; +esac + +prefix="@prefix@" +exec_prefix="@exec_prefix@" +datarootdir="@datarootdir@" +pcpath="@PKG_DEFAULT_PATH@" + +tests_init() +{ + TESTS="$@" + export TESTS + for t ; do + atf_test_case $t + done +} + +atf_init_test_cases() { + for t in ${TESTS}; do + atf_add_test_case $t + done +} diff --git a/tests/version.sh b/tests/version.sh new file mode 100755 index 00000000000..0172c0437da --- /dev/null +++ b/tests/version.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env atf-sh + +. $(atf_get_srcdir)/test_env.sh + +tests_init \ + atleast \ + exact \ + max + +atleast_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + pkgconf --atleast-version 1.0 foo + atf_check \ + -s exit:1 \ + pkgconf --atleast-version 2.0 foo +} + +exact_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --exact-version 1.0 foo + atf_check \ + pkgconf --exact-version 1.2.3 foo +} + +max_body() +{ + export PKG_CONFIG_PATH="${selfdir}/lib1" + atf_check \ + -s exit:1 \ + pkgconf --max-version 1.0 foo + atf_check \ + pkgconf --max-version 2.0 foo +} diff --git a/txt2rtf.py b/txt2rtf.py new file mode 100644 index 00000000000..111ba374214 --- /dev/null +++ b/txt2rtf.py @@ -0,0 +1,35 @@ +def text_to_rtf(input_file: str, output_file: str) -> None: + with open(input_file, "r", encoding="utf-8") as file: + text_content = file.read() + + text_content = text_content.replace("\\", "\\\\") + text_content = text_content.replace("{", "\\{") + text_content = text_content.replace("}", "\\}") + + text_content = text_content.replace("\n", "\\par\n") + + rtf_content = "{\\rtf1\\ansi\\ansicpg1252\\cocoartf2580\\cocoasubrtf220\n" + rtf_content += "{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n" + rtf_content += "\\vieww12000\\viewh15840\\viewkind0\n" + rtf_content += "\\pard\\tx720\\tx1440\\tx2160\\tx2880\\tx3600\\tx4320\\pardirnatural\\partightenfactor0\n" + rtf_content += "\\f0\\fs24 " + rtf_content += text_content + rtf_content += "\n}" + + with open(output_file, "w", encoding="utf-8") as file: + file.write(rtf_content) + + print(f"Conversion complete! RTF file saved as: {output_file}") + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 3: + print(f"Usage: python {sys.argv[0]} input.txt output.rtf") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] + + text_to_rtf(input_file, output_file)