Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
pkgconf 3.0.5
To:
tech@openbsd.org
Date:
Thu, 6 Aug 2026 07:56:44 +0200

Download raw body.

Thread
  • Theo Buehler:

    pkgconf 3.0.5

This updates pkgconf from 2.4.3 to its latest release 3.0.5.

There's been a lot of code shuffling for expanded platform support,
enabling fuzzing and testing without the kyua framework and also a
lot of new code. The NEWS file is long:

https://github.com/pkgconf/pkgconf/blob/main/NEWS

Many of these changes don't matter for us since we have pkgconf-lite.

Upstream is actively testing on OpenBSD and has added unveil support
complementing the pledge we already have. I have added a small patch
to modify the unveil code to do a single unveil("/", "r") in
unveil_search_paths() instead of fine-grained iterations over files and
directories that need to be readable. pkgconf usually runs as a normal
user with stdio rpath wpath cpath and locked unveil. Only /dev/null and
an optional log file have "rwc".

I ran this through amd64 bulks and the only port that noticed
(x11/xfce4/orage) has been fixed.

Index: AUTHORS
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/AUTHORS,v
diff -u -p -r1.1.1.1 AUTHORS
--- AUTHORS	3 May 2025 15:09:38 -0000	1.1.1.1
+++ AUTHORS	4 Aug 2026 16:19:06 -0000
@@ -1,41 +1,97 @@
+✈ Graham ✈ <plicease@cpan.org>
 A. Wilcox <AWilcox@Wilcox-Tech.com>
 Alexander Tsoy <alexander@tsoy.me>
 Alexpux <alexey.pawlow@gmail.com>
 Alon Bar-Lev <alon.barlev@gmail.com>
 Alyx <alyx@malkier.net>
-Ariadne Conill <ariadne@dereferenced.org>
+Andrea Pappacoda <andrea@pappacoda.it>
+Andrej Shadura <andrew.shadura@collabora.co.uk>
+Antonin Décimo <antonin.decimo@gmail.com>
+Ariadne Conill <ariadne@ariadne.space>
 Baptiste Daroussin <bapt@FreeBSD.org>
-Baptiste Daroussin <bapt@gandi.net>
+Ben <bhipple@protonmail.com>
 Bryan Drewery <bryan@shatow.net>
+Christoph Reiter <reiter.christoph@gmail.com>
+Colin Gillespie <colin@cgillespie.xyz>
 Dag-Erling Smørgrav <des@des.no>
 Dan Kegel <dank@kegel.com>
-Dan Kegel <dank@oblong.com>
 Dan Nicholson <dbn.lists@gmail.com>
+data-man <dataman@tutanota.com>
 David Michael <fedora.dm0@gmail.com>
+David Seifert <soap@gentoo.org>
+Doug Freed <dwfreed@mtu.edu>
+Dylan Baker <dylan@pnwbakers.com>
+Eli Schwartz <eschwartz93@gmail.com>
+Elizabeth Kiara Regina Ashford <elizabeth.jennifer.myers@gmail.com>
 Emil Renner Berthing <esmil@mailme.dk>
 Fabian Groffen <grobian@gentoo.org>
+Filipe Laíns <lains@riseup.net>
 Graham Ollis <plicease@cpan.org>
 Gregor Richards <Richards@codu.org>
+h30032433 <huyubiao@huawei.com>
+Harmen Stoppels <harmenstoppels@gmail.com>
+huyubiao <h13958451065@163.com>
 Ignacio Casal Quinteiro <qignacio@amazon.com>
 Igor Gnatenko <ignatenko@redhat.com>
+Ingo Schwarze <schwarze@openbsd.org>
+Ismael Luceno <ismael@iodev.co.uk>
 Issam Maghni <concatime@users.noreply.github.com>
-JD Horelick <jdhore1@gmail.com>
 Jason Dusek <jason.dusek@gmail.com>
 Javier Viguera <javier.viguera@digi.com>
+JD Horelick <jdhore1@gmail.com>
 Jean-Sébastien Pédron <dumbbell@FreeBSD.org>
+Jeff Moguillansky <jmoguillansky@gopro.com>
 John Hein <jhgit@users.github.com>
+Jonathan Gray <jsg@jsg.id.au>
+Joshua Watt <JPEWhacker@gmail.com>
 Jussi Pakkanen <jpakkane@gmail.com>
+Kai Pastor <dg0yt@darc.de>
+L. E. Segovia <amy@amyspark.me>
 Leorize <alaviss@users.noreply.github.com>
 Luca Barbato <lu_zero@gentoo.org>
+Marc-André Lureau <marcandre.lureau@redhat.com>
 Marcin Wojdyr <wojdyr@gmail.com>
+Mattias Hansson <mattias.hansson@assaabloy.com>
 Maxin B. John <maxinbjohn@users.noreply.github.com>
 Michał Górny <mgorny@gentoo.org>
+midipix <writeonce@midipix.org>
 Mike Frysinger <vapier@gentoo.org>
+Mike L <cl.jeremy@qq.com>
+moi15moi <moi15moismokerlolilol@gmail.com>
+mrgrouse <bdmfegys@duck.com>
+Neal Gompa <ngompa13@gmail.com>
+Nicolas Braud-Santoni <nicoo@debian.org>
+Olaf Hering <olaf@aepfle.de>
+olf <Olf0@users.noreply.github.com>
+orbea <orbea@fredslev.dk>
+Peter Kokot <peterkokot@gmail.com>
+Petr Písař <ppisar@redhat.com>
+Pierce <contact@mokou.io>
+Pierre Pronchery <pierre@defora.net>
+psykose <alice@ayaya.dev>
+rentianyue-jk <rentianyue-jk@360shuke.com>
+Ross Burton <ross.burton@arm.com>
+Ryan Scott <ryan.gl.scott@gmail.com>
+Sam James <sam@gentoo.org>
+Sandro Mani <manisandro@gmail.com>
 Seungha Yang <seungha.yang@navercorp.com>
+Shubham Chakraborty <chakrabortyshubham66@gmail.com>
+Simon Josefsson <simon@josefsson.org>
+Stefan Weil <sw@weilnetz.de>
+Stéphane Rochoy <stephane.rochoy@stormshield.eu>
+Stone Tickle <lattis@mochiro.moe>
+Taylor R Campbell <campbell+pkgconf@mumble.net>
+Timo Röhling <roehling@debian.org>
 TingPing <tingping@tingping.se>
 Tobias Kortkamp <t6@users.noreply.github.com>
+Tobias Stoeckmann <tobias@stoeckmann.org>
 Tony Theodore <tonyt@logyst.com>
+Tuukka Pasanen <tuukka.pasanen@ilmi.fi>
+Undefine <undefine@undefine.pl>
+Victor Westerhuis <victor@westerhu.is>
+Vincent Torri <vincent.torri@gmail.com>
 Volker Braun <vbraun.name@gmail.com>
+wi24rd <14029004+wi24rd@users.noreply.github.com>
+Xi Ruoyao <xry111@xry111.site>
 Yu Kobayashi <yukoba@accelart.jp>
-orbea <orbea@fredslev.dk>
-✈ Graham ✈ <plicease@cpan.org>
+Ziemowit Łąski <15880281+zlaski@users.noreply.github.com>
Index: COPYING
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/COPYING,v
diff -u -p -r1.1.1.1 COPYING
--- COPYING	3 May 2025 15:09:38 -0000	1.1.1.1
+++ COPYING	4 Aug 2026 16:19:06 -0000
@@ -1,5 +1,4 @@
-Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
-    pkgconf authors (see AUTHORS file in source directory).
+Copyright (c) 2011-2026 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
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/Makefile,v
diff -u -p -r1.2 Makefile
--- Makefile	7 May 2025 14:58:17 -0000	1.2
+++ Makefile	4 Aug 2026 16:19:06 -0000
@@ -14,17 +14,24 @@ SRCS = argvsplit.c	\
 	audit.c		\
 	bsdstubs.c	\
 	buffer.c	\
+	bufferset.c	\
+	bytecode.c	\
 	cache.c		\
 	client.c	\
+	core.c		\
 	dependency.c	\
 	fileio.c	\
 	fragment.c	\
+	license.c	\
+	output.c	\
 	parser.c	\
 	path.c		\
 	personality.c	\
 	pkg.c		\
 	queue.c		\
 	tuple.c		\
+	variable.c	\
+	version.c	\
 	getopt_long.c	\
 	main.c
 
Index: cli/core.c
===================================================================
RCS file: cli/core.c
diff -N cli/core.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ cli/core.c	4 Aug 2026 17:06:05 -0000
@@ -0,0 +1,1488 @@
+/*
+ * core.c
+ * core, printer functions
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2011-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 "libpkgconf/config.h"
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "core.h"
+#include "getopt_long.h"
+#ifndef PKGCONF_LITE
+#include "renderer-msvc.h"
+#endif
+
+static bool
+print_list_entry(const pkgconf_pkg_t *entry, void *data)
+{
+	const pkgconf_node_t *n;
+	pkgconf_client_t *client = data;
+
+	if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED)
+		return false;
+
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+			"%-30s %s - %s\n", entry->id, entry->realname, entry->description);
+
+	PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n)
+	{
+		const pkgconf_dependency_t *dep = n->data;
+
+		if (!strcmp(dep->package, entry->id))
+			continue;
+
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+			"%-30s %s - %s (provided by %s)\n", dep->package, entry->realname, entry->description, entry->id);
+	}
+
+	return false;
+}
+
+static bool
+print_package_entry(const pkgconf_pkg_t *entry, void *data)
+{
+	const pkgconf_node_t *n;
+	pkgconf_client_t *client = data;
+
+	if (entry->flags & PKGCONF_PKG_PROPF_UNINSTALLED)
+		return false;
+
+	pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, entry->id);
+
+	PKGCONF_FOREACH_LIST_ENTRY(entry->provides.head, n)
+	{
+		const pkgconf_dependency_t *dep = n->data;
+
+		if (!strcmp(dep->package, entry->id))
+			continue;
+
+		pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, dep->package);
+	}
+
+	return false;
+}
+
+static bool
+filter_cflags(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+{
+	int got_flags = 0;
+	pkgconf_cli_state_t *state = client->client_data;
+	(void) data;
+
+	if (!(state->want_flags & PKG_KEEP_SYSTEM_CFLAGS) && pkgconf_fragment_has_system_dir(client, frag))
+		return false;
+
+	if (state->want_fragment_filter != NULL && (strchr(state->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 (state->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;
+	pkgconf_cli_state_t *state = client->client_data;
+	(void) data;
+
+	if (!(state->want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag))
+		return false;
+
+	if (state->want_fragment_filter != NULL && (strchr(state->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 (state->want_flags & got_flags) != 0;
+}
+
+static void
+print_variables(pkgconf_output_t *output, pkgconf_pkg_t *pkg)
+{
+	pkgconf_node_t *node;
+
+	PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node)
+	{
+		pkgconf_tuple_t *tuple = node->data;
+
+		pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, tuple->key);
+	}
+}
+
+static void
+print_dependency_list(pkgconf_output_t *output, pkgconf_list_t *list)
+{
+	pkgconf_node_t *node;
+
+	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+	{
+		pkgconf_dependency_t *dep = node->data;
+
+		pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "%s", dep->package);
+
+		if (dep->version != NULL)
+			pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, " %s %s",
+				pkgconf_pkg_get_comparator(dep), dep->version);
+
+		pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "\n");
+	}
+}
+
+static bool
+apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
+{
+	pkgconf_node_t *iter;
+	(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_dependency_list(client->output, &pkg->provides);
+	}
+
+	return true;
+}
+
+#ifndef PKGCONF_LITE
+static void
+print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	pkgconf_node_t *node;
+	pkgconf_pkg_t **last_seen = data;
+
+	if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
+		return;
+
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" [fontname=Sans fontsize=8", pkg->id);
+
+	if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE)
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " fontcolor=gray color=gray");
+
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " label=\"%s@%s\"]\n", pkg->id, pkg->version);
+
+	if (last_seen != NULL)
+	{
+		if (*last_seen != NULL)
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"\"%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;
+
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "\"%s\" -> \"%s\" [fontname=Sans fontsize=8",
+			pkg->id, dep_id);
+
+		if (dep->flags & PKGCONF_PKG_DEPF_PRIVATE)
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, " color=gray");
+
+		pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "]");
+	}
+
+	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;
+
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+			"\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id);
+	}
+
+	if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS))
+	{
+		PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_shared.head, node)
+		{
+			pkgconf_dependency_t *dep = node->data;
+			const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package;
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=orange]\n", pkg->id, dep_id);
+		}
+	}
+}
+
+static bool
+apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+{
+	int eflag;
+	pkgconf_cli_state_t *state = client->client_data;
+	pkgconf_list_t *list = data;
+	pkgconf_pkg_t *last_seen = NULL;
+	pkgconf_node_t *iter;
+
+	pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT,
+		"digraph deptree {\n"
+		"edge [color=blue len=7.5 fontname=Sans fontsize=8]\n"
+		"node [fontname=Sans fontsize=8]\n");
+
+	if (state->want_flags & PKG_PRINT_DIGRAPH_QUERY_NODES)
+	{
+		pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT,
+			"\"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);
+
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"\"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;
+
+	pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "}");
+	return true;
+}
+
+static void
+print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	(void) unused;
+
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s (%llu)%s\n",
+		pkg->id, (unsigned long long) 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_cli_state_t *state = client->client_data;
+	pkgconf_node_t *queue_iter;
+	pkgconf_list_t *pkgq = data;
+	(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 (state->verbosity)
+					pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id);
+
+				pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, 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) 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(client->output, pkg);
+	}
+
+	return true;
+}
+
+static bool
+apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
+{
+	pkgconf_node_t *iter;
+	(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)
+			pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, pkg->filename);
+	}
+
+	return true;
+}
+
+static bool
+apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, const 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;
+		char *result;
+
+		if (pkg == NULL)
+			continue;
+
+		result = pkgconf_variable_eval_name(client, &pkg->vars, variable);
+
+		if (result != NULL)
+		{
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"%s%s", iter->prev != NULL ? " " : "", result);
+			free(result);
+		}
+	}
+
+	pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "");
+
+	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_cli_state_t *state = client->client_data;
+	pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER;
+	pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER;
+	pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER;
+	unsigned int eflag;
+	bool ret = true;
+
+	eflag = collect_fn(client, world, &unfiltered_list, maxdepth);
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+		return false;
+
+	pkgconf_fragment_filter_splice(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;
+
+	if (!pkgconf_fragment_render_buf(&filtered_list, &render_buf, true, state->want_render_ops,
+		(state->want_flags & PKG_NEWLINES) ? '\n' : ' '))
+	{
+		ret = false;
+		goto out;
+	}
+
+	ret = pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s='%s'\n",
+		prefix, pkgconf_buffer_str_or_empty(&render_buf));
+
+out:
+	pkgconf_buffer_finalize(&render_buf);
+	pkgconf_fragment_free(&unfiltered_list);
+	pkgconf_fragment_free(&filtered_list);
+
+	return ret;
+}
+
+static void
+maybe_add_module_definitions(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list)
+{
+	pkgconf_node_t *world_iter;
+	pkgconf_cli_state_t *state = client->client_data;
+
+	if ((state->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 = (char) 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)
+{
+	pkgconf_node_t *world_iter;
+	pkgconf_cli_state_t *state = client->client_data;
+
+	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 (state->want_variable != NULL && strcmp(state->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 = (char) toupper((unsigned char) *p);
+				}
+			}
+
+			char *val = pkgconf_variable_eval_str(client, &pkg->vars, tuple, NULL);
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"%s='%s'\n", havebuf, val);
+			free(val);
+		}
+	}
+}
+
+static bool
+apply_env(pkgconf_client_t *client, pkgconf_pkg_t *world, const void *env_prefix_p, int maxdepth)
+{
+	pkgconf_cli_state_t *state = client->client_data;
+	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 ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES || state->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, pkgconf_list_t *target_list, int maxdepth)
+{
+	pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER;
+	pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER;
+	int eflag;
+
+	eflag = pkgconf_pkg_cflags(client, world, &unfiltered_list, maxdepth);
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+		return false;
+
+	pkgconf_fragment_filter_splice(client, &filtered_list, &unfiltered_list, filter_cflags, NULL);
+	maybe_add_module_definitions(client, world, &filtered_list);
+
+	if (filtered_list.head == NULL)
+		goto out;
+
+	pkgconf_list_splice(target_list, &filtered_list);
+
+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, pkgconf_list_t *target_list, int maxdepth)
+{
+	pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER;
+	pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER;
+	int eflag;
+
+	eflag = pkgconf_pkg_libs(client, world, &unfiltered_list, maxdepth);
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+		return false;
+
+	pkgconf_fragment_filter_splice(client, &filtered_list, &unfiltered_list, filter_libs, NULL);
+
+	if (filtered_list.head == NULL)
+		goto out;
+
+	pkgconf_list_splice(target_list, &filtered_list);
+
+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) unused;
+	(void) maxdepth;
+
+	PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
+	{
+		pkgconf_dependency_t *dep = iter->data;
+		pkgconf_pkg_t *pkg = dep->match;
+
+		print_dependency_list(client->output, &pkg->required);
+	}
+
+	return true;
+}
+
+static bool
+apply_requires_private(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
+{
+	pkgconf_node_t *iter;
+	(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_dependency_list(client->output, &pkg->requires_private);
+	}
+	return true;
+}
+
+static void
+check_uninstalled(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	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, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	pkgconf_node_t *n;
+
+	(void) data;
+
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "node '%s' {\n", pkg->id);
+
+	if (pkg->version != NULL)
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "    version = '%s';\n", pkg->version);
+
+	PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, n)
+	{
+		pkgconf_dependency_t *dep = n->data;
+
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "    dependency '%s'", dep->package);
+
+		if (dep->compare != PKGCONF_CMP_ANY)
+		{
+			pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				" {\n"
+				"        comparator = '%s';\n"
+				"        version = '%s';\n"
+				"    };\n",
+				pkgconf_pkg_get_comparator(dep), dep->version);
+		}
+		else
+			pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, ";");
+	}
+
+	pkgconf_output_puts(client->output, PKGCONF_OUTPUT_STDOUT, "};");
+}
+
+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_output_t *output, 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)
+			pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT,
+				"%*s'-%c%s' [type %c, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->type, frag->data, frag->type, frag->children.length);
+		else
+			pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT,
+				"%*s'%s' [untyped, " SIZE_FMT_SPECIFIER " children]\n", indent, "", frag->data, frag->children.length);
+
+		print_fragment_tree_branch(output, &frag->children, indent + 2);
+	}
+
+	if (fragment_list->head != NULL)
+		pkgconf_output_puts(output, PKGCONF_OUTPUT_STDOUT, "");
+}
+
+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(client->output, &unfiltered_list, 0);
+	pkgconf_fragment_free(&unfiltered_list);
+
+	return true;
+}
+
+static void
+print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER;
+	int *retval = data;
+
+	if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
+		return;
+
+	if (pkg->license.head == NULL)
+	{
+		pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT,
+				"%s: NOASSERTION\n", pkg->id);
+	}
+	else
+	{
+		/* NOASSERTION is the default when the license is unknown, per SPDX spec § 3.15 */
+		if (!pkgconf_license_render(client, &pkg->license, &render_buf) ||
+			!pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: ", pkg->id) ||
+			!pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true))
+			*retval = EXIT_FAILURE;
+
+		pkgconf_buffer_finalize(&render_buf);
+	}
+}
+
+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 bool
+apply_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+{
+	pkgconf_list_t abis = PKGCONF_LIST_INITIALIZER;
+	pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_node_t *node;
+	int *retval = data;
+
+	if (pkgconf_pkg_link_abi(client, world, &abis, maxdepth) != PKGCONF_PKG_ERRF_OK)
+	{
+		pkgconf_bufferset_free(&abis);
+		*retval = EXIT_FAILURE;
+		return false;
+	}
+
+	PKGCONF_FOREACH_LIST_ENTRY(abis.head, node)
+	{
+		pkgconf_bufferset_t *tag = node->data;
+
+		if ((pkgconf_buffer_len(&render_buf) &&
+			 !pkgconf_buffer_push_byte(&render_buf, ' ')) ||
+			!pkgconf_buffer_append(&render_buf, pkgconf_buffer_str(&tag->buffer)))
+		{
+			pkgconf_buffer_finalize(&render_buf);
+			pkgconf_bufferset_free(&abis);
+			*retval = EXIT_FAILURE;
+			return false;
+		}
+	}
+
+	pkgconf_output_putbuf(client->output, PKGCONF_OUTPUT_STDOUT, &render_buf, true);
+
+	pkgconf_buffer_finalize(&render_buf);
+	pkgconf_bufferset_free(&abis);
+
+	return true;
+}
+
+static void
+print_license_file(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	(void) data;
+
+	if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
+		return;
+
+	/* If license file location is not available then just print empty */
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n",
+		pkg->id, pkg->license_file != NULL ? pkg->license_file : "");
+}
+
+static bool
+apply_license_file(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+{
+	int eflag;
+
+	eflag = pkgconf_pkg_traverse(client, world, print_license_file, data, maxdepth, 0);
+
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+		return false;
+
+	return true;
+}
+
+static void
+print_source(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	(void) iter_flags;
+
+	(void) data;
+
+	if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
+		return;
+
+	/* If source is empty then empty string is printed otherwise URL */
+	pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s: %s\n",
+		pkg->id, pkg->source != NULL ? pkg->source : "");
+}
+
+static bool
+apply_source(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+{
+	int eflag;
+
+	eflag = pkgconf_pkg_traverse(client, world, print_source, data, maxdepth, 0);
+
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+		return false;
+
+	return true;
+}
+
+bool
+path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim)
+{
+	pkgconf_node_t *n;
+
+	PKGCONF_FOREACH_LIST_ENTRY(list->head, n)
+	{
+		pkgconf_path_t *pn = n->data;
+
+		if (n != list->head)
+		{
+			if (!pkgconf_buffer_push_byte(buffer, delim))
+				return false;
+		}
+
+		if (!pkgconf_buffer_append(buffer, pn->path))
+			return false;
+	}
+
+	return true;
+}
+
+static void
+unveil_search_paths(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality)
+{
+	pkgconf_node_t *n;
+
+	client->unveil_handler(client, "/dev/null", "rwc");
+	client->unveil_handler(client, "/", "r");
+}
+
+static bool
+register_builtin(pkgconf_client_t *client, const char *id,
+	const pkgconf_buffer_t *pc_path_buf,
+	const pkgconf_buffer_t *pc_system_libdirs_buf,
+	const pkgconf_buffer_t *pc_system_includedirs_buf)
+{
+	pkgconf_pkg_t *pkg = calloc(1, sizeof(pkgconf_pkg_t));
+	if (pkg == NULL)
+		return false;
+
+	pkg->owner = client;
+	pkg->id = strdup(id);
+	pkg->realname = strdup(id);
+	pkg->description = strdup("virtual package defining pkgconf API version supported");
+	pkg->url = strdup(PACKAGE_BUGREPORT);
+	pkg->version = strdup(PACKAGE_VERSION);
+
+	if (pkg->id == NULL ||
+		pkg->realname == NULL ||
+		pkg->description == NULL ||
+		pkg->url == NULL ||
+		pkg->version == NULL ||
+		pkgconf_tuple_add(client, &pkg->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(pc_system_libdirs_buf), false, 0) == NULL ||
+		pkgconf_tuple_add(client, &pkg->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(pc_system_includedirs_buf), false, 0) == NULL ||
+		pkgconf_tuple_add(client, &pkg->vars, "pc_path", pkgconf_buffer_str_or_empty(pc_path_buf), false, 0) == NULL ||
+		!pkgconf_client_preload_one(client, pkg))
+	{
+		pkgconf_pkg_free(client, pkg);
+		return false;
+	}
+
+	return true;
+}
+
+/* SAFETY: pkgconf_client_t takes ownership of successfully registered package objects */
+static void
+register_builtins(pkgconf_client_t *client, const pkgconf_cross_personality_t *personality)
+{
+	pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!path_list_to_buffer(&personality->dir_list, &pc_path_buf, ':') ||
+		!path_list_to_buffer(&personality->filter_libdirs, &pc_system_libdirs_buf, ':') ||
+		!path_list_to_buffer(&personality->filter_includedirs, &pc_system_includedirs_buf, ':'))
+		goto error;
+
+	if (!register_builtin(client, "pkg-config", &pc_path_buf, &pc_system_libdirs_buf, &pc_system_includedirs_buf) ||
+		!register_builtin(client, "pkgconf", &pc_path_buf, &pc_system_libdirs_buf, &pc_system_includedirs_buf))
+		goto error;
+
+error:
+	pkgconf_buffer_finalize(&pc_path_buf);
+	pkgconf_buffer_finalize(&pc_system_libdirs_buf);
+	pkgconf_buffer_finalize(&pc_system_includedirs_buf);
+}
+
+#ifndef PKGCONF_LITE
+static void
+dump_personality(pkgconf_output_t *output, const pkgconf_cross_personality_t *p)
+{
+	pkgconf_buffer_t pc_path_buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!path_list_to_buffer(&p->dir_list, &pc_path_buf, ':') ||
+		!path_list_to_buffer(&p->filter_libdirs, &pc_system_libdirs_buf, ':') ||
+		!path_list_to_buffer(&p->filter_includedirs, &pc_system_includedirs_buf, ':'))
+		goto out;
+
+	pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "Triplet: %s\n", p->name);
+
+	if (p->sysroot_dir)
+		pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SysrootDir: %s\n", p->sysroot_dir);
+
+	pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "DefaultSearchPaths: %s\n", pc_path_buf.base);
+	pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemIncludePaths: %s\n", pc_system_includedirs_buf.base);
+	pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "SystemLibraryPaths: %s\n", pc_system_libdirs_buf.base);
+
+	if (p->want_default_pure)
+		pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultPure: true\n");
+
+	if (p->want_default_static)
+		pkgconf_output_fmt(output, PKGCONF_OUTPUT_STDOUT, "WantDefaultStatic: true\n");
+
+out:
+	pkgconf_buffer_finalize(&pc_path_buf);
+	pkgconf_buffer_finalize(&pc_system_libdirs_buf);
+	pkgconf_buffer_finalize(&pc_system_includedirs_buf);
+}
+#endif
+
+int
+pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc)
+{
+	(void) argc;
+
+	int ret = EXIT_SUCCESS;
+	unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE;
+	const char *builddir;
+	const char *sysroot_dir;
+	pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
+	pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER;
+	pkgconf_node_t *node;
+	pkgconf_buffer_t queryparams = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_pkg_t world = {
+		.id = "virtual:world",
+		.realname = "virtual world package",
+		.flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
+	};
+
+#ifndef PKGCONF_LITE
+	if ((state->want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY)
+	{
+		dump_personality(state->pkg_client.output, state->pkg_client.personality);
+
+		ret = EXIT_SUCCESS;
+		goto out;
+	}
+#endif
+
+#ifndef PKGCONF_LITE
+	if ((state->want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX)
+		state->want_render_ops = msvc_renderer_get();
+#endif
+
+	if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_FDO_SYSROOT_RULES"))
+		want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES;
+
+	if (pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PKGCONF1_SYSROOT_RULES"))
+		want_client_flags |= PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES;
+
+	if ((state->want_flags & PKG_SHORT_ERRORS) == PKG_SHORT_ERRORS)
+		want_client_flags |= PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS;
+
+	if ((state->want_flags & PKG_DONT_RELOCATE_PATHS) == PKG_DONT_RELOCATE_PATHS)
+		want_client_flags |= PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS;
+
+	state->error_msgout = stderr;
+	if ((state->want_flags & PKG_ERRORS_ON_STDOUT) == PKG_ERRORS_ON_STDOUT)
+		state->error_msgout = stdout;
+	if ((state->want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS) {
+		state->error_msgout = fopen(PATH_DEV_NULL, "w");
+		state->opened_error_msgout = true;
+	}
+
+	if ((state->want_flags & PKG_IGNORE_CONFLICTS) == PKG_IGNORE_CONFLICTS || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_IGNORE_CONFLICTS") != NULL)
+		want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS;
+
+	if ((state->want_flags & PKG_STATIC) == PKG_STATIC || state->pkg_client.personality->want_default_static)
+		want_client_flags |= (PKGCONF_PKG_PKGF_SEARCH_PRIVATE | PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS);
+
+	if ((state->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 <https://github.com/pkgconf/pkgconf/issues/83> for rationale.
+	 */
+	if ((state->want_flags & PKG_PURE) == PKG_PURE || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_PURE_DEPGRAPH") != NULL || state->pkg_client.personality->want_default_pure)
+		want_client_flags &= ~PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS;
+
+	if ((state->want_flags & PKG_ENV_ONLY) == PKG_ENV_ONLY)
+		want_client_flags |= PKGCONF_PKG_PKGF_ENV_ONLY;
+
+	if ((state->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 ((state->want_flags & PKG_DEFINE_PREFIX) == PKG_DEFINE_PREFIX || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_RELOCATE_PATHS") != NULL)
+#endif
+		want_client_flags |= PKGCONF_PKG_PKGF_REDEFINE_PREFIX;
+
+	if ((state->want_flags & PKG_NO_UNINSTALLED) == PKG_NO_UNINSTALLED || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DISABLE_UNINSTALLED") != NULL)
+		want_client_flags |= PKGCONF_PKG_PKGF_NO_UNINSTALLED;
+
+	if ((state->want_flags & PKG_NO_PROVIDES) == PKG_NO_PROVIDES)
+		want_client_flags |= PKGCONF_PKG_PKGF_SKIP_PROVIDES;
+
+	if ((state->want_flags & PKG_DONT_DEFINE_PREFIX) == PKG_DONT_DEFINE_PREFIX  || pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_DONT_DEFINE_PREFIX") != NULL)
+		want_client_flags &= ~PKGCONF_PKG_PKGF_REDEFINE_PREFIX;
+
+	if ((state->want_flags & PKG_INTERNAL_CFLAGS) == PKG_INTERNAL_CFLAGS)
+		want_client_flags |= PKGCONF_PKG_PKGF_DONT_FILTER_INTERNAL_CFLAGS;
+
+	/* --static --libs, --exists require the full dependency graph to be solved */
+	if ((state->want_flags & (PKG_STATIC|PKG_LIBS)) == (PKG_STATIC|PKG_LIBS) || (state->want_flags & PKG_EXISTS) == PKG_EXISTS)
+		want_client_flags |= PKGCONF_PKG_PKGF_REQUIRE_INTERNAL;
+
+	/* 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.
+	 */
+	const bool querying_flattened_metadata =
+		(state->want_flags & PKG_MODVERSION) == PKG_MODVERSION ||
+		(state->want_flags & PKG_REQUIRES) == PKG_REQUIRES ||
+		(state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE ||
+		(state->want_flags & PKG_PROVIDES) == PKG_PROVIDES ||
+		(state->want_flags & PKG_VARIABLES) == PKG_VARIABLES ||
+		(state->want_flags & PKG_PATH) == PKG_PATH ||
+		state->want_variable != NULL;
+
+	if (querying_flattened_metadata)
+		state->maximum_traverse_depth = 1;
+
+	/* metadata queries only report properties of the modules the user asked about, they
+	 * never combine those modules into a build, so 'Conflicts' rules between them are not
+	 * relevant.  see <https://github.com/pkgconf/pkgconf/issues/580>.
+	 */
+	if (querying_flattened_metadata ||
+		(state->want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE ||
+		(state->want_flags & PKG_DUMP_LICENSE_FILE) == PKG_DUMP_LICENSE_FILE ||
+		(state->want_flags & PKG_DUMP_SOURCE) == PKG_DUMP_SOURCE)
+		want_client_flags |= PKGCONF_PKG_PKGF_SKIP_CONFLICTS;
+
+	/* if we are asking for a variable, path or list of variables, this only makes sense
+	 * for a single package.
+	 */
+	if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES ||
+		(state->want_flags & PKG_PATH) == PKG_PATH ||
+		state->want_variable != NULL)
+	{
+		state->maximum_package_count = 1;
+	}
+
+	if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE ||
+		(state->want_flags & PKG_CFLAGS))
+	{
+		want_client_flags |= PKGCONF_PKG_PKGF_SEARCH_PRIVATE;
+	}
+
+	if ((builddir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_TOP_BUILD_DIR")) != NULL)
+		pkgconf_client_set_buildroot_dir(&state->pkg_client, builddir);
+
+	if ((sysroot_dir = pkgconf_client_getenv(&state->pkg_client, "PKG_CONFIG_SYSROOT_DIR")) != NULL)
+	{
+		const char *destdir;
+
+		pkgconf_client_set_sysroot_dir(&state->pkg_client, sysroot_dir);
+
+		if ((destdir = pkgconf_client_getenv(&state->pkg_client, "DESTDIR")) != NULL)
+		{
+			if (!strcmp(destdir, sysroot_dir))
+				want_client_flags |= PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES |
+					PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION;
+		}
+	}
+
+	/* we have determined what features we want most likely.  in some cases, we override later. */
+	pkgconf_client_set_flags(&state->pkg_client, want_client_flags);
+
+	/* at this point, want_client_flags should be set, so build the dir list */
+	pkgconf_client_dir_list_build(&state->pkg_client, state->pkg_client.personality);
+
+	/* unveil the entire search path now that we have loaded the personality data and built the dir list. */
+	unveil_search_paths(&state->pkg_client, state->pkg_client.personality);
+
+	/* register built-in packages */
+	register_builtins(&state->pkg_client, state->pkg_client.personality);
+
+	/* preload any files in PKG_CONFIG_PRELOADED_FILES */
+	pkgconf_client_preload_from_environ(&state->pkg_client, "PKG_CONFIG_PRELOADED_FILES");
+
+	if (state->required_pkgconfig_version != NULL)
+	{
+		if (pkgconf_compare_version(PACKAGE_VERSION, state->required_pkgconfig_version) >= 0)
+			ret = EXIT_SUCCESS;
+		else
+			ret = EXIT_FAILURE;
+
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_LIST) == PKG_LIST)
+	{
+		pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_list_entry);
+		ret = EXIT_SUCCESS;
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES)
+	{
+		pkgconf_scan_all(&state->pkg_client, &state->pkg_client, print_package_entry);
+		ret = EXIT_SUCCESS;
+		goto out;
+	}
+
+	while (last_argc < argc && argv[last_argc])
+	{
+		if ((pkgconf_buffer_len(&queryparams) > 0 &&
+			 !pkgconf_buffer_push_byte(&queryparams, ' ')) ||
+			!pkgconf_buffer_append(&queryparams, argv[last_argc]))
+		{
+			pkgconf_buffer_finalize(&queryparams);
+			ret = EXIT_FAILURE;
+			goto out;
+		}
+
+		last_argc++;
+	}
+
+	pkgconf_dependency_parse_str(&state->pkg_client, &deplist, pkgconf_buffer_str_or_empty(&queryparams), 0);
+	pkgconf_buffer_finalize(&queryparams);
+
+	if (state->required_module_version != NULL || state->required_exact_module_version != NULL || state->required_max_module_version != NULL)
+	{
+		const char *target_version = NULL;
+		pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY;
+
+		if (state->required_module_version != NULL)
+		{
+			target_version = state->required_module_version;
+			compare = PKGCONF_CMP_GREATER_THAN_EQUAL;
+		}
+		else if (state->required_exact_module_version != NULL)
+		{
+			target_version = state->required_exact_module_version;
+			compare = PKGCONF_CMP_EQUAL;
+		}
+		else if (state->required_max_module_version != NULL)
+		{
+			target_version = state->required_max_module_version;
+			compare = PKGCONF_CMP_LESS_THAN_EQUAL;
+		}
+
+		PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
+		{
+			pkgconf_dependency_t *dep = node->data;
+
+			/* already constrained at query level */
+			if (dep->compare != PKGCONF_CMP_ANY)
+				continue;
+
+			dep->compare = compare;
+			dep->version = strdup(target_version);
+			if (dep->version == NULL)
+			{
+				pkgconf_dependency_free(&deplist);
+				ret = EXIT_FAILURE;
+				goto out;
+			}
+		}
+	}
+
+	PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
+	{
+		pkgconf_dependency_t *dep = node->data;
+
+		/* 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 (state->maximum_package_count > 0 && pkgq.length >= state->maximum_package_count)
+			break;
+
+		pkgconf_queue_push_dependency(&pkgq, dep);
+	}
+
+	pkgconf_dependency_free(&deplist);
+
+	if (pkgq.head == NULL)
+	{
+		pkgconf_output_puts(state->pkg_client.output, PKGCONF_OUTPUT_STDERR,
+			"Please specify at least one package name on the command line.");
+		ret = EXIT_FAILURE;
+		goto out;
+	}
+
+	ret = EXIT_SUCCESS;
+
+	if (!pkgconf_queue_solve(&state->pkg_client, &pkgq, &world, state->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 */
+	state->pkg_client.unveil_handler(&state->pkg_client, NULL, NULL);
+
+#ifndef PKGCONF_LITE
+	if ((state->want_flags & PKG_SIMULATE) == PKG_SIMULATE)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+
+		pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS);
+		apply_simulate(&state->pkg_client, &world, NULL, -1);
+	}
+#endif
+
+	if ((state->want_flags & PKG_VALIDATE) == PKG_VALIDATE)
+		goto out;
+
+	if ((state->want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE)
+	{
+		apply_license(&state->pkg_client, &world, &ret, 2);
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_LINK_ABI) == PKG_LINK_ABI)
+	{
+		/* private dependencies only contribute their ABI when statically linked */
+		if (!(state->want_flags & PKG_STATIC))
+			pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
+
+		apply_link_abi(&state->pkg_client, &world, &ret, 2);
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_DUMP_LICENSE_FILE) == PKG_DUMP_LICENSE_FILE)
+	{
+		apply_license_file(&state->pkg_client, &world, &ret, 2);
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_DUMP_SOURCE) == PKG_DUMP_SOURCE)
+	{
+		apply_source(&state->pkg_client, &world, &ret, 2);
+		goto out;
+	}
+
+
+	if ((state->want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED)
+	{
+		ret = EXIT_FAILURE;
+		apply_uninstalled(&state->pkg_client, &world, &ret, 2);
+		goto out;
+	}
+
+	if (state->want_env_prefix != NULL)
+	{
+		apply_env(&state->pkg_client, &world, state->want_env_prefix, 2);
+		goto out;
+	}
+
+	if ((state->want_flags & PKG_PROVIDES) == PKG_PROVIDES)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_provides(&state->pkg_client, &world, NULL, 2);
+	}
+
+#ifndef PKGCONF_LITE
+	if ((state->want_flags & PKG_DIGRAPH) == PKG_DIGRAPH)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_digraph(&state->pkg_client, &world, &pkgq, 2);
+	}
+
+	if ((state->want_flags & PKG_SOLUTION) == PKG_SOLUTION)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_print_solution(&state->pkg_client, &world, NULL, 2);
+	}
+#endif
+
+	if ((state->want_flags & PKG_MODVERSION) == PKG_MODVERSION)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_modversion(&state->pkg_client, &world, &pkgq, 2);
+	}
+
+	if ((state->want_flags & PKG_PATH) == PKG_PATH)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+
+		pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL);
+		apply_path(&state->pkg_client, &world, NULL, 2);
+	}
+
+	if ((state->want_flags & PKG_VARIABLES) == PKG_VARIABLES)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_variables(&state->pkg_client, &world, NULL, 2);
+	}
+
+	if (state->want_variable != NULL)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+
+		pkgconf_client_set_flags(&state->pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL);
+		apply_variable(&state->pkg_client, &world, state->want_variable, 2);
+	}
+
+	if ((state->want_flags & PKG_REQUIRES) == PKG_REQUIRES)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+		apply_requires(&state->pkg_client, &world, NULL, 2);
+	}
+
+	if ((state->want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE)
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+
+		apply_requires_private(&state->pkg_client, &world, NULL, 2);
+	}
+
+	if ((state->want_flags & PKG_FRAGMENT_TREE))
+	{
+		state->want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
+
+		apply_fragment_tree(&state->pkg_client, &world, NULL, 2);
+	}
+
+	if ((state->want_flags & (PKG_CFLAGS|PKG_LIBS)))
+	{
+		pkgconf_list_t target_list = PKGCONF_LIST_INITIALIZER;
+		pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER;
+
+		if ((state->want_flags & PKG_CFLAGS))
+			apply_cflags(&state->pkg_client, &world, &target_list, 2);
+
+		if ((state->want_flags & PKG_LIBS) && !(state->want_flags & PKG_STATIC))
+			pkgconf_client_set_flags(&state->pkg_client, state->pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
+
+		if ((state->want_flags & PKG_LIBS))
+			apply_libs(&state->pkg_client, &world, &target_list, 2);
+
+		if (!pkgconf_fragment_render_buf(&target_list, &render_buf, true, state->want_render_ops,
+			(state->want_flags & PKG_NEWLINES) ? '\n' : ' ') ||
+			!pkgconf_output_putbuf(state->pkg_client.output, PKGCONF_OUTPUT_STDOUT, &render_buf, true))
+			ret = EXIT_FAILURE;
+		pkgconf_buffer_finalize(&render_buf);
+
+		pkgconf_fragment_free(&target_list);
+	}
+
+out:
+	pkgconf_solution_free(&state->pkg_client, &world);
+	pkgconf_queue_free(&pkgq);
+	pkgconf_cli_state_reset(state);
+
+	return ret;
+}
+
+void
+pkgconf_cli_state_reset(pkgconf_cli_state_t *state)
+{
+	pkgconf_cross_personality_deinit((void *) state->pkg_client.personality);
+	pkgconf_client_deinit(&state->pkg_client);
+
+	if (state->logfile_out != NULL)
+		fclose(state->logfile_out);
+	if (state->opened_error_msgout)
+		fclose(state->error_msgout);
+}
Index: cli/core.h
===================================================================
RCS file: cli/core.h
diff -N cli/core.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ cli/core.h	4 Aug 2026 16:20:03 -0000
@@ -0,0 +1,103 @@
+/*
+ * core.h
+ * core, printer functions
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2011-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.
+ */
+
+#ifndef __CLI_CORE_H
+#define __CLI_CORE_H
+
+#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)
+#define PKG_DUMP_SOURCE			(((uint64_t) 1) << 49)
+#define PKG_DUMP_LICENSE_FILE		(((uint64_t) 1) << 50)
+#define PKG_NEWLINES			(((uint64_t) 1) << 51)
+#define PKG_PRINT_DIGRAPH_QUERY_NODES	(((uint64_t) 1) << 52)
+#define PKG_LINK_ABI			(((uint64_t) 1) << 53)
+
+typedef struct {
+	pkgconf_client_t pkg_client;
+	pkgconf_fragment_render_ops_t *want_render_ops;
+
+	uint64_t want_flags;
+	int verbosity;
+	int maximum_traverse_depth;
+	size_t maximum_package_count;
+
+	const char *want_variable;
+	const char *want_fragment_filter;
+	const char *want_env_prefix;
+
+	char *required_pkgconfig_version;
+	const char *required_exact_module_version;
+	const char *required_max_module_version;
+	const char *required_module_version;
+
+	FILE *error_msgout;
+	FILE *logfile_out;
+
+	bool opened_error_msgout;
+} pkgconf_cli_state_t;
+
+extern bool path_list_to_buffer(const pkgconf_list_t *list, pkgconf_buffer_t *buffer, char delim);
+extern int pkgconf_cli_run(pkgconf_cli_state_t *state, int argc, char *argv[], int last_argc);
+extern void pkgconf_cli_state_reset(pkgconf_cli_state_t *state);
+
+#endif
Index: cli/getopt_long.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/cli/getopt_long.c,v
diff -u -p -r1.1.1.1 getopt_long.c
--- cli/getopt_long.c	3 May 2025 15:09:38 -0000	1.1.1.1
+++ cli/getopt_long.c	4 Aug 2026 16:19:06 -0000
@@ -1,7 +1,8 @@
-/*	$OpenBSD: getopt_long.c,v 1.1.1.1 2025/05/03 15:09:38 tb Exp $	*/
+/*	$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 $	*/
 
-/*
+/* SPDX-License-Identifier: LicenseRef-scancode-sudo AND BSD-4-Clause
+ *
  * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
Index: cli/getopt_long.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/cli/getopt_long.h,v
diff -u -p -r1.1.1.1 getopt_long.h
--- cli/getopt_long.h	3 May 2025 15:09:38 -0000	1.1.1.1
+++ cli/getopt_long.h	4 Aug 2026 16:19:06 -0000
@@ -2,6 +2,8 @@
 /*	$FreeBSD$ */
 
 /*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
  * Copyright (c) 2000 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
Index: cli/main.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/cli/main.c,v
diff -u -p -r1.2 main.c
--- cli/main.c	9 May 2025 06:07:50 -0000	1.2
+++ cli/main.c	4 Aug 2026 16:19:06 -0000
@@ -1,9 +1,10 @@
 /*
  * main.c
- * main() routine, printer functions
+ * main() routine
  *
- * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
- *     pkgconf authors (see AUTHORS).
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2011-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
@@ -18,820 +19,100 @@
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
 #include "getopt_long.h"
-#ifndef PKGCONF_LITE
-#include "renderer-msvc.h"
-#endif
-#ifdef _WIN32
-#include <io.h>     /* for _setmode() */
-#include <fcntl.h>
-#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;
-}
+#include "core.h"
 
 static bool
-filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+parse_maximum_traverse_depth(const char *value, int *depth)
 {
-	int got_flags = 0;
-	(void) client;
-	(void) data;
+	char *end;
+	long parsed;
 
-	if (!(want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag))
-		return false;
+	errno = 0;
+	parsed = strtol(value, &end, 10);
 
-	if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type))
+	if (value == end || *end != '\0' || errno == ERANGE || parsed < -1 || parsed > INT_MAX)
 		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);
-	}
-
+	*depth = (int) parsed;
 	return true;
 }
 
-#ifndef PKGCONF_LITE
-static void
-print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+static const char *
+environ_lookup_handler(const pkgconf_client_t *client, const char *key)
 {
-	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);
-	}
+	return getenv(key);
 }
 
 static bool
-apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
+error_handler(const char *msg, const pkgconf_client_t *client, void *data)
 {
-	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);
+	(void) data;
+	pkgconf_cli_state_t *state = client->client_data;
 
-	if (eflag != PKGCONF_PKG_ERRF_OK)
-		return false;
+	if (state->error_msgout == NULL)
+		return true;
 
-	printf("}\n");
+	pkgconf_output_file_fmt(state->error_msgout, "%s", msg);
 	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)
+unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions)
 {
-	pkgconf_node_t *iter;
 	(void) client;
-	(void) unused;
-	(void) maxdepth;
 
-	PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
+	if (pkgconf_unveil(path, permissions) == -1 && errno != ENOENT)
 	{
-		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);
+		pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
+		exit(EXIT_FAILURE);
 	}
-
-	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)
+static pkgconf_cross_personality_t *
+deduce_personality(char *argv[])
 {
-	int eflag;
-
-	eflag = pkgconf_pkg_traverse(client, world, print_graph_node, data, maxdepth, 0);
+	const char *argv0 = argv[0];
+	char *i, *prefix;
+	pkgconf_cross_personality_t *out;
 
-	if (eflag != PKGCONF_PKG_ERRF_OK)
-		return false;
+	i = strrchr(argv0, '/');
+	if (i != NULL)
+		argv0 = i + 1;
 
-	return true;
-}
+#if defined(_WIN32) || defined(_WIN64)
+	i = strrchr(argv0, '\\');
+	if (i != NULL)
+		argv0 = i + 1;
 #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);
+	i = strstr(argv0, "-pkg");
+	if (i == NULL)
+		return pkgconf_cross_personality_default();
 
-		print_fragment_tree_branch(&frag->children, indent + 2);
-	}
+	prefix = pkgconf_strndup(argv0, i - argv0);
+	out = pkgconf_cross_personality_find(prefix);
+	free(prefix);
+	if (out == NULL)
+		return pkgconf_cross_personality_default();
 
-	if (fragment_list->head != NULL)
-		printf("\n");
+	return out;
 }
+#endif
 
-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;
-}
+#ifdef _WIN32
+static UINT original_console_cp;
+static UINT original_console_out_cp;
 
 static void
-print_license(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+restore_console_code_page(void)
 {
-	(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;
+	SetConsoleCP(original_console_cp);
+	SetConsoleOutputCP(original_console_out_cp);
 }
+#endif
 
 static void
 version(void)
@@ -843,8 +124,7 @@ 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("Copyright (c) 2011-2026 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");
@@ -885,7 +165,6 @@ usage(void)
 	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
@@ -903,7 +182,7 @@ usage(void)
 	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("  --maximum-traverse-depth          maximum allowed depth for dependency graph (-1 for unlimited)\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");
@@ -931,6 +210,7 @@ usage(void)
 	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("  --print-digraph-query-nodes       also print query nodes in '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);
@@ -938,131 +218,57 @@ usage(void)
 	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();
+	printf("  --license                         print the specified module's license to stdout if known\n");
+	printf("  --link-abi                        print the link ABIs (e.g. c, c++) the module must be linked against\n");
+	printf("  --source                          print the specified module's source code location to stdout if known\n");
+	printf("  --exists-cflags                   add -DHAVE_FOO fragments to cflags for each found module\n");
 
-	prefix = pkgconf_strndup(argv0, i - argv0);
-	out = pkgconf_cross_personality_find(prefix);
-	free(prefix);
-	if (out == NULL)
-		return pkgconf_cross_personality_default();
+	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("  --newlines                        use newlines for whitespace between fragments\n");
 
-	return out;
+	printf("\nreport bugs to <%s>.\n", PACKAGE_BUGREPORT);
 }
-#endif
 
 int
 main(int argc, char *argv[])
 {
+#ifdef _WIN32
+	// When activeCodePage is set to UTF-8 in the application manifest (requires Windows 1903+),
+	// GetACP() returns CP_UTF8 but the console code pages are not automatically updated to match.
+	// Detect this condition via GetACP() == CP_UTF8 and align the console code pages accordingly.
+	// Restoring on exit is safe: if ACP is already CP_UTF8, resetting to the saved values is a no-op.
+	if (GetACP() == CP_UTF8)
+	{
+		original_console_cp = GetConsoleCP();
+		original_console_out_cp = GetConsoleOutputCP();
+		SetConsoleCP(CP_UTF8);
+		SetConsoleOutputCP(CP_UTF8);
+		atexit(restore_console_code_page);
+	}
+#endif
+
 	int ret;
-	pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
+	pkgconf_cli_state_t state = {
+		.want_flags = 0,
+		.maximum_traverse_depth = 256,
+	};
 	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", NULL) == -1)
+	if (pkgconf_pledge("stdio rpath wpath cpath unveil", NULL) == -1)
 	{
-		fprintf(stderr, "pkgconf: pledge failed: %s\n", strerror(errno));
+		pkgconf_output_file_fmt(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
@@ -1074,85 +280,91 @@ main(int argc, char *argv[])
 #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, },
+		{ "version", no_argument, &state.want_flags, PKG_VERSION|PKG_PRINT_ERRORS, },
+		{ "about", no_argument, &state.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, },
+		{ "libs", no_argument, &state.want_flags, PKG_LIBS|PKG_PRINT_ERRORS, },
+		{ "cflags", no_argument, &state.want_flags, PKG_CFLAGS|PKG_PRINT_ERRORS, },
+		{ "modversion", no_argument, &state.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, },
+		{ "exists", no_argument, &state.want_flags, PKG_EXISTS, },
+		{ "print-errors", no_argument, &state.want_flags, PKG_PRINT_ERRORS, },
+		{ "short-errors", no_argument, &state.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, },
+		{ "static", no_argument, &state.want_flags, PKG_STATIC, },
+		{ "shared", no_argument, &state.want_flags, PKG_SHARED, },
+		{ "pure", no_argument, &state.want_flags, PKG_PURE, },
+		{ "print-requires", no_argument, &state.want_flags, PKG_REQUIRES, },
+		{ "print-variables", no_argument, &state.want_flags, PKG_VARIABLES|PKG_PRINT_ERRORS, },
+#ifndef PKGCONF_LITE
+		{ "digraph", no_argument, &state.want_flags, PKG_DIGRAPH, },
+		{ "solution", no_argument, &state.want_flags, PKG_SOLUTION, },
+#endif
+		{ "help", no_argument, &state.want_flags, PKG_HELP, },
+		{ "env-only", no_argument, &state.want_flags, PKG_ENV_ONLY, },
+		{ "print-requires-private", no_argument, &state.want_flags, PKG_REQUIRES_PRIVATE, },
+		{ "cflags-only-I", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_I|PKG_PRINT_ERRORS, },
+		{ "cflags-only-other", no_argument, &state.want_flags, PKG_CFLAGS_ONLY_OTHER|PKG_PRINT_ERRORS, },
+		{ "libs-only-L", no_argument, &state.want_flags, PKG_LIBS_ONLY_LDPATH|PKG_PRINT_ERRORS, },
+		{ "libs-only-l", no_argument, &state.want_flags, PKG_LIBS_ONLY_LIBNAME|PKG_PRINT_ERRORS, },
+		{ "libs-only-other", no_argument, &state.want_flags, PKG_LIBS_ONLY_OTHER|PKG_PRINT_ERRORS, },
+		{ "uninstalled", no_argument, &state.want_flags, PKG_UNINSTALLED, },
+		{ "no-uninstalled", no_argument, &state.want_flags, PKG_NO_UNINSTALLED, },
+		{ "keep-system-cflags", no_argument, &state.want_flags, PKG_KEEP_SYSTEM_CFLAGS, },
+		{ "keep-system-libs", no_argument, &state.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 },
+		{ "ignore-conflicts", no_argument, &state.want_flags, PKG_IGNORE_CONFLICTS, },
+		{ "errors-to-stdout", no_argument, &state.want_flags, PKG_ERRORS_ON_STDOUT, },
+		{ "silence-errors", no_argument, &state.want_flags, PKG_SILENCE_ERRORS, },
+		{ "list-all", no_argument, &state.want_flags, PKG_LIST|PKG_PRINT_ERRORS, },
+		{ "list-package-names", no_argument, &state.want_flags, PKG_LIST_PACKAGE_NAMES|PKG_PRINT_ERRORS, },
+#ifndef PKGCONF_LITE
+		{ "simulate", no_argument, &state.want_flags, PKG_SIMULATE, },
+#endif
+		{ "no-cache", no_argument, &state.want_flags, PKG_NO_CACHE, },
+		{ "print-provides", no_argument, &state.want_flags, PKG_PROVIDES, },
+		{ "no-provides", no_argument, &state.want_flags, PKG_NO_PROVIDES, },
+		{ "debug", no_argument, &state.want_flags, PKG_DEBUG|PKG_PRINT_ERRORS, },
+		{ "validate", no_argument, &state.want_flags, PKG_VALIDATE|PKG_PRINT_ERRORS|PKG_ERRORS_ON_STDOUT },
 		{ "log-file", required_argument, NULL, 40 },
-		{ "path", no_argument, &want_flags, PKG_PATH },
+		{ "path", no_argument, &state.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 },
+		{ "define-prefix", no_argument, &state.want_flags, PKG_DEFINE_PREFIX },
+		{ "dont-define-prefix", no_argument, &state.want_flags, PKG_DONT_DEFINE_PREFIX },
+		{ "dont-relocate-paths", no_argument, &state.want_flags, PKG_DONT_RELOCATE_PATHS },
 		{ "env", required_argument, NULL, 48 },
 #ifndef PKGCONF_LITE
-		{ "msvc-syntax", no_argument, &want_flags, PKG_MSVC_SYNTAX },
+		{ "msvc-syntax", no_argument, &state.want_flags, PKG_MSVC_SYNTAX },
 #endif
 		{ "fragment-filter", required_argument, NULL, 50 },
-		{ "internal-cflags", no_argument, &want_flags, PKG_INTERNAL_CFLAGS },
+		{ "internal-cflags", no_argument, &state.want_flags, PKG_INTERNAL_CFLAGS },
 #ifndef PKGCONF_LITE
-		{ "dump-personality", no_argument, &want_flags, PKG_DUMP_PERSONALITY },
+		{ "dump-personality", no_argument, &state.want_flags, PKG_DUMP_PERSONALITY },
 		{ "personality", required_argument, NULL, 53 },
 #endif
-		{ "license", no_argument, &want_flags, PKG_DUMP_LICENSE },
+		{ "license", no_argument, &state.want_flags, PKG_DUMP_LICENSE },
+		{ "license-file", no_argument, &state.want_flags, PKG_DUMP_LICENSE_FILE },
+		{ "link-abi", no_argument, &state.want_flags, PKG_LINK_ABI },
 		{ "verbose", no_argument, NULL, 55 },
-		{ "exists-cflags", no_argument, &want_flags, PKG_EXISTS_CFLAGS },
-		{ "fragment-tree", no_argument, &want_flags, PKG_FRAGMENT_TREE },
+		{ "exists-cflags", no_argument, &state.want_flags, PKG_EXISTS_CFLAGS },
+		{ "fragment-tree", no_argument, &state.want_flags, PKG_FRAGMENT_TREE },
+		{ "source", no_argument, &state.want_flags, PKG_DUMP_SOURCE },
+		{ "newlines", no_argument, &state.want_flags, PKG_NEWLINES },
+#ifndef PKGCONF_LITE
+		{ "print-digraph-query-nodes", no_argument, &state.want_flags, PKG_PRINT_DIGRAPH_QUERY_NODES },
+#endif
 		{ 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);
+		state.error_msgout = stderr;
+		pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL);
 	}
 #endif
 
@@ -1161,25 +373,30 @@ main(int argc, char *argv[])
 		switch (ret)
 		{
 		case 2:
-			required_module_version = pkg_optarg;
+			state.required_module_version = pkg_optarg;
 			break;
 		case 3:
-			required_pkgconfig_version = pkg_optarg;
+			state.required_pkgconfig_version = pkg_optarg;
 			break;
 		case 7:
-			want_variable = pkg_optarg;
+			state.want_variable = pkg_optarg;
 			break;
 		case 11:
-			maximum_traverse_depth = atoi(pkg_optarg);
+			if (!parse_maximum_traverse_depth(pkg_optarg, &state.maximum_traverse_depth))
+			{
+				pkgconf_output_file_fmt(stderr, "pkgconf: invalid maximum traverse depth: %s\n", pkg_optarg);
+				ret = EXIT_FAILURE;
+				goto out;
+			}
 			break;
 		case 27:
-			pkgconf_tuple_define_global(&pkg_client, pkg_optarg);
+			pkgconf_tuple_define_global(&state.pkg_client, pkg_optarg);
 			break;
 		case 28:
-			required_exact_module_version = pkg_optarg;
+			state.required_exact_module_version = pkg_optarg;
 			break;
 		case 29:
-			required_max_module_version = pkg_optarg;
+			state.required_max_module_version = pkg_optarg;
 			break;
 		case 40:
 			logfile_arg = pkg_optarg;
@@ -1188,16 +405,13 @@ main(int argc, char *argv[])
 			pkgconf_path_prepend(pkg_optarg, &dir_list, true);
 			break;
 		case 43:
-			pkgconf_client_set_prefix_varname(&pkg_client, pkg_optarg);
+			pkgconf_client_set_prefix_varname(&state.pkg_client, pkg_optarg);
 			break;
-		case 45:
-			relocate_path(pkg_optarg);
-			return EXIT_SUCCESS;
 		case 48:
-			want_env_prefix = pkg_optarg;
+			state.want_env_prefix = pkg_optarg;
 			break;
 		case 50:
-			want_fragment_filter = pkg_optarg;
+			state.want_fragment_filter = pkg_optarg;
 			break;
 #ifndef PKGCONF_LITE
 		case 53:
@@ -1205,7 +419,7 @@ main(int argc, char *argv[])
 			break;
 #endif
 		case 55:
-			verbosity++;
+			state.verbosity++;
 			break;
 		case '?':
 		case ':':
@@ -1224,48 +438,53 @@ main(int argc, char *argv[])
 #endif
 	}
 
-	pkgconf_path_copy_list(&personality->dir_list, &dir_list);
-	pkgconf_path_free(&dir_list);
-
-#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);
+	pkgconf_client_options_t client_options = {
+		.error_handler = error_handler,
+		.error_handler_data = &state,
+		.personality = personality,
+		.client_data = &state,
+		.environ_lookup_handler = environ_lookup_handler,
+		.unveil_handler = unveil_handler,
+	};
+	pkgconf_client_init_with_options(&state.pkg_client, &client_options);
 
 #ifndef PKGCONF_LITE
-	if ((want_flags & PKG_MSVC_SYNTAX) == PKG_MSVC_SYNTAX || getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL)
-		want_render_ops = msvc_renderer_get();
+	if (getenv("PKG_CONFIG_MSVC_SYNTAX") != NULL)
+		state.want_flags |= PKG_MSVC_SYNTAX;
 #endif
 
-	if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL)
-		maximum_traverse_depth = atoi(env_traverse_depth);
+	if ((env_traverse_depth = getenv("PKG_CONFIG_MAXIMUM_TRAVERSE_DEPTH")) != NULL &&
+		!parse_maximum_traverse_depth(env_traverse_depth, &state.maximum_traverse_depth))
+	{
+		pkgconf_output_file_fmt(stderr, "pkgconf: invalid maximum traverse depth: %s\n", env_traverse_depth);
+		ret = EXIT_FAILURE;
+		goto out;
+	}
 
-	if ((want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS)
-		want_flags |= (PKG_SILENCE_ERRORS);
+	if ((state.want_flags & PKG_PRINT_ERRORS) != PKG_PRINT_ERRORS)
+		state.want_flags |= (PKG_SILENCE_ERRORS);
 
-	if ((want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW"))
-		want_flags |= (PKG_SILENCE_ERRORS);
+	if ((state.want_flags & PKG_SILENCE_ERRORS) == PKG_SILENCE_ERRORS && !getenv("PKG_CONFIG_DEBUG_SPEW"))
+		state.want_flags |= (PKG_SILENCE_ERRORS);
 	else
-		want_flags &= ~(PKG_SILENCE_ERRORS);
+		state.want_flags &= ~(PKG_SILENCE_ERRORS);
 
 	if (getenv("PKG_CONFIG_DONT_RELOCATE_PATHS"))
-		want_flags |= (PKG_DONT_RELOCATE_PATHS);
+		state.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);
+	if ((state.want_flags & PKG_VALIDATE) == PKG_VALIDATE || (state.want_flags & PKG_DEBUG) == PKG_DEBUG)
+		pkgconf_client_set_warn_handler(&state.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);
+	if ((state.want_flags & PKG_DEBUG) == PKG_DEBUG)
+		pkgconf_client_set_trace_handler(&state.pkg_client, error_handler, NULL);
 #endif
 
-	if ((want_flags & PKG_ABOUT) == PKG_ABOUT)
+	pkgconf_path_prepend_list(&state.pkg_client.dir_list, &dir_list);
+	pkgconf_path_free(&dir_list);
+
+	if ((state.want_flags & PKG_ABOUT) == PKG_ABOUT)
 	{
 		about();
 
@@ -1273,7 +492,7 @@ main(int argc, char *argv[])
 		goto out;
 	}
 
-	if ((want_flags & PKG_VERSION) == PKG_VERSION)
+	if ((state.want_flags & PKG_VERSION) == PKG_VERSION)
 	{
 		version();
 
@@ -1281,7 +500,7 @@ main(int argc, char *argv[])
 		goto out;
 	}
 
-	if ((want_flags & PKG_HELP) == PKG_HELP)
+	if ((state.want_flags & PKG_HELP) == PKG_HELP)
 	{
 		usage();
 
@@ -1289,471 +508,30 @@ main(int argc, char *argv[])
 		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 <https://github.com/pkgconf/pkgconf/issues/83> 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);
-
-	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)
 	{
-		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
+		if (pkgconf_unveil(logfile_arg, "rwc") == -1)
 		{
-			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);
+			pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
+			return EXIT_FAILURE;
 		}
-	}
-
-	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;
-	}
 
-#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);
+		state.logfile_out = fopen(logfile_arg, "a");
+		pkgconf_audit_set_log(&state.pkg_client, state.logfile_out);
 	}
 
-	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);
+	if (getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS") != NULL)
+		state.want_flags |= PKG_KEEP_SYSTEM_CFLAGS;
 
-		apply_libs(&pkg_client, &world, NULL, 2);
-	}
+	if (getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS") != NULL)
+		state.want_flags |= PKG_KEEP_SYSTEM_LIBS;
 
-	if (want_flags & (PKG_CFLAGS|PKG_LIBS))
-		printf("\n");
+	return pkgconf_cli_run(&state, argc, argv, pkg_optind);
 
 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);
-
+	pkgconf_cli_state_reset(&state);
 	return ret;
 }
Index: cli/tuple.c
===================================================================
RCS file: cli/tuple.c
diff -N cli/tuple.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ cli/tuple.c	4 Aug 2026 16:20:16 -0000
@@ -0,0 +1,351 @@
+/*
+ * tuple.c
+ * management of key->value tuples
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * 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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !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);
+}
+
+/*
+ * !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 *
+ */
+const char *
+pkgconf_tuple_find_global(pkgconf_client_t *client, const char *key)
+{
+	pkgconf_variable_t *v;
+	bool saw_sysroot = false;
+
+	if (client == NULL || key == NULL)
+		return NULL;
+
+	v = pkgconf_variable_find(&client->global_vars, key);
+
+	pkgconf_buffer_reset(&client->_scratch_buffer);
+	(void) pkgconf_variable_eval(client, &client->global_vars, v, &client->_scratch_buffer, &saw_sysroot);
+
+	return pkgconf_buffer_str_or_empty(&client->_scratch_buffer);
+}
+
+/*
+ * !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;
+
+	if (workbuf == NULL)
+		goto out;
+
+	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 char *
+dequote(const char *value)
+{
+	char *buf = calloc(1, (strlen(value) + 1) * 2);
+	char *bptr = buf;
+	const char *i;
+	char quote = 0;
+
+	if (buf == NULL)
+		return NULL;
+
+	if (*value == '\'' || *value == '"')
+		quote = *value;
+
+	for (i = value; *i != '\0'; i++)
+	{
+		/* An escaped whitespace denotes a literal whitespace, e.g. a path
+		 * component containing a space produced by `printf %q`.  Store the
+		 * canonical (unescaped) text so it is not left escaped in memory:
+		 * otherwise it would be escaped a second time when rendered into a
+		 * fragment ("a\ dir" would come out as "a\\\ dir").  Other backslash
+		 * sequences are preserved verbatim so variable-expansion escapes such
+		 * as "\${var}" keep working.
+		 *
+		 * See <https://github.com/pkgconf/pkgconf/issues/575>.
+		 */
+		if (*i == '\\' && isspace((unsigned char) *(i + 1)))
+		{
+			i++;
+			*bptr++ = *i;
+		}
+		else if (*i == '\\' && quote && *(i + 1) == quote)
+		{
+			i++;
+			*bptr++ = *i;
+		}
+		else if (*i != quote)
+			*bptr++ = *i;
+	}
+
+	return buf;
+}
+
+/*
+ * !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)
+ *
+ *    Wrapper around pkgconf_variable_get_or_create(list, key) and bytecode compiler.
+ *
+ *    :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_buffer_t rhs_bcbuf = PKGCONF_BUFFER_INITIALIZER;
+
+	(void) client;
+
+	if (list == NULL || key == NULL || value == NULL)
+		return NULL;
+
+	dequote_value = dequote(value);
+	if (dequote_value == NULL)
+		return NULL;
+
+	pkgconf_variable_t *v = pkgconf_variable_get_or_create(list, key);
+	if (v == NULL)
+	{
+		free(dequote_value);
+		return NULL;
+	}
+
+	if (!parse)
+	{
+		if (!pkgconf_bytecode_emit_text(&rhs_bcbuf, dequote_value, strlen(dequote_value)))
+		{
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			free(dequote_value);
+			return NULL;
+		}
+
+		free(dequote_value);
+	}
+	else
+	{
+		if (!pkgconf_bytecode_compile(&rhs_bcbuf, dequote_value))
+		{
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			free(dequote_value);
+			return NULL;
+		}
+
+		free(dequote_value);
+	}
+
+	/* ugh, we are doing var=${var}/foo stuff */
+	if (parse && pkgconf_bytecode_references_var(&rhs_bcbuf, key))
+	{
+		pkgconf_buffer_t old_bcbuf = PKGCONF_BUFFER_INITIALIZER;
+		pkgconf_buffer_t new_bcbuf = PKGCONF_BUFFER_INITIALIZER;
+
+		/* preserve the old bytecode */
+		if (!pkgconf_buffer_copy(&v->bcbuf, &old_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			return NULL;
+		}
+
+		/* splice the selfrefs, using the old bytecode instead of ${var} */
+		if (!pkgconf_bytecode_rewrite_selfrefs(&new_bcbuf, &rhs_bcbuf, key, &old_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&new_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+
+			return NULL;
+		}
+
+		/* copy the spliced bytecode back to &rhs_bcbuf, replacing its contents */
+		if (!pkgconf_buffer_copy(&new_bcbuf, &rhs_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&new_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			return NULL;
+		}
+
+		pkgconf_buffer_finalize(&old_bcbuf);
+		pkgconf_buffer_finalize(&new_bcbuf);
+	}
+
+	pkgconf_buffer_finalize(&v->bcbuf);
+	v->bcbuf = rhs_bcbuf;
+	v->flags = flags;
+	pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
+
+	return (pkgconf_tuple_t *) v;
+}
+
+/*
+ * !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 *
+ */
+const char *
+pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
+{
+	pkgconf_variable_t *v;
+
+	if (client == NULL || list == NULL || key == NULL)
+		return NULL;
+
+	v = pkgconf_variable_find(list, key);
+	if (v == NULL)
+		v = pkgconf_variable_find(&client->global_vars, key);
+
+	pkgconf_buffer_reset(&client->_scratch_buffer);
+
+	(void) pkgconf_variable_eval(client, list, v, &client->_scratch_buffer, NULL);
+
+	return pkgconf_buffer_str_or_empty(&client->_scratch_buffer);
+}
+
+/*
+ * !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);
+	pkgconf_variable_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);
+}
Index: libpkgconf/argvsplit.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/argvsplit.c,v
diff -u -p -r1.1.1.1 argvsplit.c
--- libpkgconf/argvsplit.c	3 May 2025 15:09:38 -0000	1.1.1.1
+++ libpkgconf/argvsplit.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * argvsplit.c
  * argv_split() routine
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012, 2017 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -39,27 +41,28 @@
 void
 pkgconf_argv_free(char **argv)
 {
+	if (argv == NULL)
+		return;
+
 	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.
+ * Walk `src`, breaking it at whitespace which is neither quoted nor escaped.
  *
- *    :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
+ * When `raw` is set, the quoting and escaping is only read to find those breaks:
+ * the quotes and backslashes are copied into the argument along with everything
+ * else, leaving it spelled exactly as it was written.  Otherwise they are
+ * consumed, as a shell would consume them.
  */
-int
-pkgconf_argv_split(const char *src, int *argc, char ***argv)
+static int
+argv_split(const char *src, int *argc, char ***argv, bool raw)
 {
-	char *buf = malloc(strlen(src) + 1);
+	char *buf = calloc(1, strlen(src) + 1);
+	if (buf == NULL)
+		return -1;
+
 	const char *src_iter;
 	char *dst_iter;
 	int argc_count = 0;
@@ -70,9 +73,13 @@ pkgconf_argv_split(const char *src, int 
 	src_iter = src;
 	dst_iter = buf;
 
-	memset(buf, 0, strlen(src) + 1);
-
 	*argv = calloc(argv_size, sizeof (void *));
+	if (*argv == NULL)
+	{
+		free(buf);
+		return -1;
+	}
+
 	(*argv)[argc_count] = dst_iter;
 
 	while (*src_iter)
@@ -80,7 +87,7 @@ pkgconf_argv_split(const char *src, int 
 		if (escaped)
 		{
 			/* POSIX: only \CHAR is special inside a double quote if CHAR is {$, `, ", \, newline}. */
-			if (quote == '"')
+			if (quote == '"' && !raw)
 			{
 				if (!(*src_iter == '$' || *src_iter == '`' || *src_iter == '"' || *src_iter == '\\'))
 					*dst_iter++ = '\\';
@@ -97,9 +104,19 @@ pkgconf_argv_split(const char *src, int 
 		else if (quote)
 		{
 			if (*src_iter == quote)
+			{
 				quote = 0;
+
+				if (raw)
+					*dst_iter++ = *src_iter;
+			}
 			else if (*src_iter == '\\' && quote != '\'')
+			{
 				escaped = true;
+
+				if (raw)
+					*dst_iter++ = *src_iter;
+			}
 			else
 				*dst_iter++ = *src_iter;
 		}
@@ -111,8 +128,19 @@ pkgconf_argv_split(const char *src, int 
 
 				if (argc_count == argv_size)
 				{
+					char **new_argv;
+
 					argv_size += 5;
-					*argv = realloc(*argv, sizeof(void *) * argv_size);
+					new_argv = pkgconf_reallocarray(*argv, argv_size, sizeof(void *));
+					if (new_argv == NULL)
+					{
+						free(*argv);
+						free(buf);
+						*argv = NULL;
+						return -1;
+					}
+
+					*argv = new_argv;
 				}
 
 				(*argv)[argc_count] = dst_iter;
@@ -122,11 +150,17 @@ pkgconf_argv_split(const char *src, int 
 		{
 			case '\\':
 				escaped = true;
+
+				if (raw)
+					*dst_iter++ = *src_iter;
 				break;
 
 			case '\"':
 			case '\'':
 				quote = *src_iter;
+
+				if (raw)
+					*dst_iter++ = *src_iter;
 				break;
 
 			default:
@@ -141,6 +175,7 @@ pkgconf_argv_split(const char *src, int 
 	{
 		free(*argv);
 		free(buf);
+		*argv = NULL;
 		return -1;
 	}
 
@@ -151,4 +186,49 @@ pkgconf_argv_split(const char *src, int 
 
 	*argc = argc_count;
 	return 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_argv_split(const char *src, int *argc, char ***argv)
+ *
+ *    Splits a string into an argument vector, consuming shell quoting and
+ *    backslash escapes the way a shell would.
+ *
+ *    :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)
+{
+	return argv_split(src, argc, argv, false);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_argv_split_raw(const char *src, int *argc, char ***argv)
+ *
+ *    Splits a string into an argument vector without interpreting it: the
+ *    quoting and escaping says where the arguments end, but is left in the text.
+ *
+ *    This is for input which still has to be expanded.  Consuming the quoting
+ *    here would consume only what the input itself spells, leaving whatever a
+ *    variable substitutes into it unconsumed, so it is left for a second pass
+ *    over the finished text.
+ *
+ *    :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_raw(const char *src, int *argc, char ***argv)
+{
+	return argv_split(src, argc, argv, true);
 }
Index: libpkgconf/audit.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/audit.c,v
diff -u -p -r1.1.1.1 audit.c
--- libpkgconf/audit.c	3 May 2025 15:09:38 -0000	1.1.1.1
+++ libpkgconf/audit.c	4 Aug 2026 16:20:54 -0000
@@ -2,6 +2,8 @@
  * audit.c
  * package audit log functions
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2016 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -14,6 +16,7 @@
  */
 
 #include <libpkgconf/libpkgconf.h>
+#include <errno.h>
 
 /*
  * !doc
@@ -66,7 +69,11 @@ pkgconf_audit_log(pkgconf_client_t *clie
 		return;
 
 	va_start(va, format);
-	vfprintf(client->auditf, format, va);
+	if (!pkgconf_output_file_vfmt(client->auditf, format, va))
+	{
+		// TODO: should probably return here
+		pkgconf_error(client, "Failed to output to file: %s", strerror(errno));
+	}
 	va_end(va);
 }
 
@@ -88,11 +95,24 @@ pkgconf_audit_log_dependency(pkgconf_cli
 	if (client->auditf == NULL)
 		return;
 
-	fprintf(client->auditf, "%s ", dep->id);
+	if (!pkgconf_output_file_fmt(client->auditf, "%s ", dep->id))
+	{
+		// TODO: should probably return here
+		pkgconf_error(client, "Failed to output to file: %s", strerror(errno));
+	}
+
 	if (depnode->version != NULL && depnode->compare != PKGCONF_CMP_ANY)
 	{
-		fprintf(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version);
+		if (!pkgconf_output_file_fmt(client->auditf, "%s %s ", pkgconf_pkg_get_comparator(depnode), depnode->version))
+		{
+			// TODO: should probably return here
+			pkgconf_error(client, "Failed to output to file: %s", strerror(errno));
+		}
 	}
 
-	fprintf(client->auditf, "[%s]\n", dep->version);
+	if (!pkgconf_output_file_fmt(client->auditf, "[%s]\n", dep->version))
+	{
+		// TODO: should probably return here
+		pkgconf_error(client, "Failed to output to file: %s", strerror(errno));
+	}
 }
Index: libpkgconf/bsdstubs.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/bsdstubs.c,v
diff -u -p -r1.2 bsdstubs.c
--- libpkgconf/bsdstubs.c	9 May 2025 06:07:51 -0000	1.2
+++ libpkgconf/bsdstubs.c	4 Aug 2026 16:20:54 -0000
@@ -1,103 +1,7 @@
-/*	$OpenBSD: bsdstubs.c,v 1.2 2025/05/09 06:07:51 tb Exp $	*/
-/*	$OpenBSD: bsdstubs.c,v 1.2 2025/05/09 06:07:51 tb Exp $	*/
-
 /*
- * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
- *
- * 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.
+ * SPDX-License-Identifier: pkgconf
  *
- * 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 <stdlib.h>
-#include <sys/types.h>
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include <libpkgconf/bsdstubs.h>
-#include <libpkgconf/config.h>
-
-#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 <nenolod@dereferenced.org>.
+ * Copyright (c) 2012 Ariadne Conill <ariadne@dereferenced.org>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -108,23 +12,39 @@ strlcat(char *dst, const char *src, size
  * from the use of this software.
  */
 
-#if !HAVE_DECL_STRNDUP
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/bsdstubs.h>
+#include <libpkgconf/config.h>
+
+#if HAVE_DECL_STRNDUP
+# define pkgconf_strndup_impl strndup
+#else
 /*
  * 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.
+ * If 'len' is less than the length of the source string, truncation occurred.
  */
 static inline char *
-strndup(const char *src, size_t len)
+pkgconf_strndup_impl(const char *src, size_t len)
 {
-	char *out = malloc(len + 1);
-	pkgconf_strlcpy(out, src, len + 1);
+	const char *end = memchr(src, '\0', len);
+	size_t n = end != NULL ? (size_t)(end - src) : len;
+	char *out = malloc(n + 1);
+
+	if (out == NULL)
+		return NULL;
+
+	memcpy(out, src, n);
+	out[n] = '\0';
+
 	return out;
 }
 #endif
 
-#if !HAVE_DECL_PLEDGE
+#if HAVE_DECL_PLEDGE
+# define pkgconf_pledge_impl pledge
+#else
 static inline int
-pledge(const char *promises, const char *execpromises)
+pkgconf_pledge_impl(const char *promises, const char *execpromises)
 {
 	(void) promises;
 	(void) execpromises;
@@ -133,9 +53,11 @@ pledge(const char *promises, const char 
 }
 #endif
 
-#if !HAVE_DECL_UNVEIL
+#if HAVE_DECL_UNVEIL
+# define pkgconf_unveil_impl unveil
+#else
 static inline int
-unveil(const char *path, const char *permissions)
+pkgconf_unveil_impl(const char *path, const char *permissions)
 {
 	(void) path;
 	(void) permissions;
@@ -144,27 +66,11 @@ unveil(const char *path, const char *per
 }
 #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 HAVE_DECL_REALLOCARRAY
+# define pkgconf_reallocarray_impl reallocarray
+#else
+static inline void *
+pkgconf_reallocarray_impl(void *ptr, size_t m, size_t n)
 {
 	if (n && m > -1 / n)
 	{
@@ -176,20 +82,26 @@ reallocarray(void *ptr, size_t m, size_t
 }
 #endif
 
+char *
+pkgconf_strndup(const char *src, size_t len)
+{
+	return pkgconf_strndup_impl(src, len);
+}
+
 void *
 pkgconf_reallocarray(void *ptr, size_t m, size_t n)
 {
-	return reallocarray(ptr, m, n);
+	return pkgconf_reallocarray_impl(ptr, m, n);
 }
 
 int
 pkgconf_pledge(const char *promises, const char *execpromises)
 {
-	return pledge(promises, execpromises);
+	return pkgconf_pledge_impl(promises, execpromises);
 }
 
 int
 pkgconf_unveil(const char *path, const char *permissions)
 {
-	return unveil(path, permissions);
+	return pkgconf_unveil_impl(path, permissions);
 }
Index: libpkgconf/bsdstubs.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/bsdstubs.h,v
diff -u -p -r1.2 bsdstubs.h
--- libpkgconf/bsdstubs.h	9 May 2025 06:07:51 -0000	1.2
+++ libpkgconf/bsdstubs.h	4 Aug 2026 16:20:54 -0000
@@ -2,6 +2,8 @@
  * bsdstubs.h
  * Header for stub BSD function prototypes if unavailable on a specific platform.
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012 William Pitcock <nenolod@dereferenced.org>.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -15,6 +17,8 @@
 
 #ifndef LIBPKGCONF_BSDSTUBS_H
 #define LIBPKGCONF_BSDSTUBS_H
+
+#include <stddef.h>
 
 #include <libpkgconf/libpkgconf-api.h>
 
Index: libpkgconf/buffer.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/buffer.c,v
diff -u -p -r1.1.1.1 buffer.c
--- libpkgconf/buffer.c	3 May 2025 15:09:38 -0000	1.1.1.1
+++ libpkgconf/buffer.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * buffer.c
  * dynamically-managed buffers
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2024 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -26,62 +28,701 @@
  * dynamically-allocated buffers.
  */
 
-static inline size_t
-target_allocation_size(size_t target_size)
+/* buffers below BUFFER_LINEAR_GROWTH_MAX increment by 128 bytes each time */
+#define BUFFER_LINEAR_GROWTH_MAX 4096
+
+static inline bool
+target_allocation_size(size_t target_size, size_t *allocation_size)
 {
-	return 4096 + (4096 * (target_size / 4096));
+	if (target_size < BUFFER_LINEAR_GROWTH_MAX)
+	{
+		*allocation_size = target_size + (128 - (target_size % 128));
+		return true;
+	}
+
+	size_t cap = BUFFER_LINEAR_GROWTH_MAX;
+	while (cap <= target_size)
+	{
+		if (cap > SIZE_MAX / 2)
+			return false;
+
+		cap *= 2;
+	}
+
+	*allocation_size = cap;
+	return true;
 }
 
-void
-pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text)
+static inline bool
+buffer_reserve(pkgconf_buffer_t *buffer, size_t content_len)
 {
-	size_t needed = strlen(text) + 1;
-	size_t newsize = pkgconf_buffer_len(buffer) + needed;
+	size_t new_cap, old_cap;
+	char *newbase;
 
-	char *newbase = realloc(buffer->base, target_allocation_size(newsize));
+	if (!target_allocation_size(content_len, &new_cap))
+		return false;
 
-	/* XXX: silently failing here is antisocial */
-	if (newbase == NULL)
-		return;
+	if (buffer->base != NULL && target_allocation_size(pkgconf_buffer_len(buffer), &old_cap) && old_cap == new_cap)
+		return true;
 
-	char *newend = newbase + pkgconf_buffer_len(buffer);
-	pkgconf_strlcpy(newend, text, needed);
+	newbase = realloc(buffer->base, new_cap);
+	if (newbase == NULL)
+		return false;
 
 	buffer->base = newbase;
-	buffer->end = newend + needed;
+	return true;
 }
 
-void
+static bool
+buffer_storage_overlaps(const pkgconf_buffer_t *buffer, const void *source, size_t source_size)
+{
+	if (buffer->base == NULL || source == NULL || source_size == 0)
+		return false;
+
+	size_t buffer_size = pkgconf_buffer_len(buffer) + 1;
+	uintptr_t buffer_start = (uintptr_t) buffer->base;
+	uintptr_t source_start = (uintptr_t) source;
+
+	if (buffer_start > UINTPTR_MAX - (buffer_size - 1) ||
+		source_start > UINTPTR_MAX - (source_size - 1))
+		return true;
+
+	uintptr_t buffer_end = buffer_start + buffer_size - 1;
+	uintptr_t source_end = source_start + source_size - 1;
+
+	return buffer_start <= source_end && source_start <= buffer_end;
+}
+
+#if 0
+static void
+buffer_debug(pkgconf_buffer_t *buffer)
+{
+	for (char *c = buffer->base; c <= buffer->end; c++)
+	{
+		fprintf(stderr, "%02x ", (unsigned char) *c);
+	}
+
+	pkgconf_output_file_fmt(stderr, "\n");
+}
+#endif
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text)
+ *
+ *    Append a null-terminated string to the buffer, reallocating as necessary.
+ *    The storage occupied by *text* must not overlap the buffer.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to append to.
+ *    :param char *text: The null-terminated string to append.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text)
+{
+	size_t len = pkgconf_buffer_len(buffer);
+	size_t text_len = strlen(text);
+
+	if (text_len == SIZE_MAX)
+		return false;
+
+	if (buffer_storage_overlaps(buffer, text, text_len + 1))
+		return false;
+
+	if (len > SIZE_MAX - text_len)
+		return false;
+
+	size_t new_len = len + text_len;
+	if (!buffer_reserve(buffer, new_len))
+		return false;
+
+	memcpy(buffer->base + len, text, text_len);
+
+	buffer->end = buffer->base + new_len;
+	*buffer->end = '\0';
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n)
+ *
+ *    Append a slice of *n* bytes to the buffer. Does nothing if *n* is zero.
+ *    The source slice must not overlap the buffer.
+ *
+ *    :param pkgconf_buffer_t *buf: The buffer to append to.
+ *    :param char *p: Pointer to the byte sequence to append.
+ *    :param size_t n: Number of bytes to append.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n)
+{
+	size_t len = pkgconf_buffer_len(buf);
+
+	if (n == 0)
+		return true;
+
+	if (buffer_storage_overlaps(buf, p, n))
+		return false;
+
+	if (len > SIZE_MAX - n)
+		return false;
+
+	size_t new_len = len + n;
+	if (!buffer_reserve(buf, new_len))
+		return false;
+
+	memcpy(buf->base + len, p, n);
+
+	buf->end = buf->base + new_len;
+	*buf->end = '\0';
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va)
+ *
+ *    Append a formatted string to the buffer using a :code:`va_list`.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to append to.
+ *    :param char *fmt: A printf-style format string.
+ *    :param va_list src_va: The variadic argument list for the format string.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list src_va)
+{
+	char stackbuf[256];
+	va_list va;
+	int formatted_len;
+
+	va_copy(va, src_va);
+	formatted_len = vsnprintf(stackbuf, sizeof stackbuf, fmt, va);
+	va_end(va);
+
+	if (formatted_len < 0)
+		return false;
+
+	if ((size_t) formatted_len < sizeof stackbuf)
+		return pkgconf_buffer_append_slice(buffer, stackbuf, (size_t) formatted_len);
+
+	size_t len = pkgconf_buffer_len(buffer);
+
+	if (!buffer_reserve(buffer, len + (size_t) formatted_len))
+		return false;
+
+	va_copy(va, src_va);
+	vsnprintf(buffer->base + len, (size_t) formatted_len + 1, fmt, va);
+	va_end(va);
+
+	buffer->end = buffer->base + len + (size_t) formatted_len;
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...)
+ *
+ *    Append a formatted string to the buffer using variadic arguments.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to append to.
+ *    :param char *fmt: A printf-style format string.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	bool ret = pkgconf_buffer_append_vfmt(buffer, fmt, va);
+	va_end(va);
+
+	return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text)
+ *
+ *    Prepend a null-terminated string to the beginning of the buffer.
+ *    If *text* is NULL, the buffer contents are unchanged.
+ *    The storage occupied by *text* must not overlap the buffer.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to prepend to.
+ *    :param char *text: The null-terminated string to prepend, or NULL.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text)
+{
+	pkgconf_buffer_t tmpbuf = PKGCONF_BUFFER_INITIALIZER;
+	bool ret = true;
+
+	if (text != NULL)
+	{
+		size_t text_len = strlen(text);
+
+		if (text_len == SIZE_MAX ||
+			buffer_storage_overlaps(buffer, text, text_len + 1))
+			return false;
+	}
+
+	if (text != NULL && !pkgconf_buffer_append(&tmpbuf, text))
+		return false;
+
+	if (!pkgconf_buffer_append(&tmpbuf, pkgconf_buffer_str_or_empty(buffer)))
+	{
+		pkgconf_buffer_finalize(&tmpbuf);
+		return false;
+	}
+
+	if (pkgconf_buffer_len(&tmpbuf))
+		ret = pkgconf_buffer_copy(&tmpbuf, buffer);
+
+	pkgconf_buffer_finalize(&tmpbuf);
+
+	return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte)
+ *
+ *    Append a single byte to the buffer, reallocating as necessary.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to append to.
+ *    :param char byte: The byte to append.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
 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));
+	size_t len = pkgconf_buffer_len(buffer);
 
-	/* XXX: silently failing here remains antisocial */
-	if (newbase == NULL)
-		return;
+	if (len == SIZE_MAX)
+		return false;
 
-	char *newend = newbase + newsize;
-	*(newend - 1) = byte;
-	*newend = '\0';
+	size_t new_len = len + 1;
+	if (!buffer_reserve(buffer, new_len))
+		return false;
 
-	buffer->base = newbase;
-	buffer->end = newend;
+	buffer->base[len] = byte;
+
+	buffer->end = buffer->base + new_len;
+	*buffer->end = '\0';
+
+	return true;
 }
 
-void
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_trim_byte(pkgconf_buffer_t *buffer)
+ *
+ *    Remove the last byte from the buffer. The buffer must be non-empty.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to trim.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
 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));
+	size_t len = pkgconf_buffer_len(buffer);
+	if (len == 0)
+		return false;
+
+	size_t new_len = len - 1;
+	if (!buffer_reserve(buffer, new_len))
+		return false;
 
-	buffer->base = newbase;
-	buffer->end = newbase + newsize;
+	buffer->end = buffer->base + new_len;
 	*(buffer->end) = '\0';
+
+	return true;
 }
 
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_buffer_finalize(pkgconf_buffer_t *buffer)
+ *
+ *    Free all memory owned by the buffer and reset it to an empty state.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to finalize.
+ *    :return: nothing
+ */
 void
 pkgconf_buffer_finalize(pkgconf_buffer_t *buffer)
 {
 	free(buffer->base);
+	buffer->base = buffer->end = NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out)
+ *
+ *    Write the buffer contents followed by a newline to a file stream.
+ *    If the buffer is empty, only a newline is written.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to write.
+ *    :param FILE *out: The output file stream.
+ *    :return: :code:`true` on success, :code:`false` on I/O error.
+ *             :code:`errno` will be set by fputs/fputc.
+ */
+bool
+pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out)
+{
+	if (pkgconf_buffer_len(buffer) != 0)
+	{
+		if (fputs(pkgconf_buffer_str(buffer), out) == EOF)
+			return false;
+	}
+
+	if (fputc('\n', out) == EOF)
+		return false;
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va)
+ *
+ *    Join a NULL-terminated list of strings into the buffer, separated by *delim*.
+ *    Uses a :code:`va_list` for the string arguments.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to join into.
+ *    :param char delim: The delimiter byte inserted between each argument.
+ *    :param va_list src_va: The variadic argument list of :code:`const char *` strings, terminated by NULL.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list src_va)
+{
+	va_list va;
+	const char *arg;
+
+	va_copy(va, src_va);
+
+	while ((arg = va_arg(va, const char *)) != NULL)
+	{
+		if (pkgconf_buffer_str(buffer) != NULL)
+		{
+			if (!pkgconf_buffer_push_byte(buffer, delim))
+			{
+				va_end(va);
+				return false;
+			}
+		}
+
+		if (!pkgconf_buffer_append(buffer, arg))
+		{
+			va_end(va);
+			return false;
+		}
+	}
+
+	va_end(va);
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...)
+ *
+ *    Join a NULL-terminated list of strings into the buffer, separated by *delim*.
+ *    The *delim* parameter is typed as :code:`int` due to C variadic promotion rules.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to join into.
+ *    :param int delim: The delimiter byte inserted between each argument (cast to :code:`char` internally).
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...)
+{
+	va_list va;
+
+	va_start(va, delim);
+	bool ret = pkgconf_buffer_vjoin(buffer, (char)delim, va);
+	va_end(va);
+
+	return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix)
+ *
+ *    Test whether the buffer begins with the contents of *prefix*.
+ *
+ *    :param pkgconf_buffer_t *haystack: The buffer to search in.
+ *    :param pkgconf_buffer_t *prefix: The prefix to test for.
+ *    :return: :code:`true` if *haystack* starts with *prefix*, :code:`false` otherwise.
+ */
+bool
+pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix)
+{
+	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
+	const char *prefix_str = pkgconf_buffer_str_or_empty(prefix);
+
+	return strncmp(haystack_str, prefix_str, strlen(prefix_str)) == 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
+ *
+ *    Test whether the buffer contains the contents of *needle* as a substring.
+ *
+ *    :param pkgconf_buffer_t *haystack: The buffer to search in.
+ *    :param pkgconf_buffer_t *needle: The substring to search for.
+ *    :return: :code:`true` if *needle* is found, :code:`false` otherwise.
+ */
+bool
+pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
+{
+	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
+	const char *needle_str = pkgconf_buffer_str_or_empty(needle);
+
+	return strstr(haystack_str, needle_str) != NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle)
+ *
+ *    Test whether the buffer contains a given byte.
+ *
+ *    :param pkgconf_buffer_t *haystack: The buffer to search in.
+ *    :param char needle: The byte to search for.
+ *    :return: :code:`true` if *needle* is found, :code:`false` otherwise.
+ */
+
+bool
+pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle)
+{
+	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
+	return strchr(haystack_str, needle) != NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
+ *
+ *    Test whether two buffers have identical contents.
+ *
+ *    :param pkgconf_buffer_t *haystack: The first buffer.
+ *    :param pkgconf_buffer_t *needle: The second buffer.
+ *    :return: :code:`true` if the buffers have the same length and contents, :code:`false` otherwise.
+ */
+bool
+pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle)
+{
+	const char *haystack_str = pkgconf_buffer_str_or_empty(haystack);
+	const char *needle_str = pkgconf_buffer_str_or_empty(needle);
+
+	if (pkgconf_buffer_len(haystack) != pkgconf_buffer_len(needle))
+		return false;
+
+	return memcmp(haystack_str, needle_str, pkgconf_buffer_len(haystack)) == 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value)
+ *
+ *    Copy *src* into *dest*, replacing all occurrences of *pattern* with *value*.
+ *    If *pattern* is empty, *src* is appended to *dest* unmodified.
+ *    Neither *src*, *pattern* nor *value* may overlap *dest*.
+ *
+ *    :param pkgconf_buffer_t *dest: The destination buffer.
+ *    :param pkgconf_buffer_t *src: The source buffer.
+ *    :param char *pattern: The pattern string to search for.
+ *    :param char *value: The replacement string.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value)
+{
+	const char *iter = src->base;
+
+	if (dest == src ||
+		buffer_storage_overlaps(dest, src->base, pkgconf_buffer_len(src) + 1))
+		return false;
+
+	if (pattern == NULL)
+		pattern = "";
+
+	if (value == NULL)
+		value = "";
+
+	size_t pattern_len = strlen(pattern);
+	if (buffer_storage_overlaps(dest, pattern, pattern_len + 1) ||
+		buffer_storage_overlaps(dest, value, strlen(value) + 1))
+		return false;
+
+	if (!pkgconf_buffer_len(src))
+		return true;
+
+	if (!pattern_len)
+		return pkgconf_buffer_append(dest, pkgconf_buffer_str(src));
+
+	while (iter < src->end)
+	{
+		if ((size_t)(src->end - iter) >= pattern_len && !memcmp(iter, pattern, pattern_len))
+		{
+			if (!pkgconf_buffer_append(dest, value))
+				return false;
+
+			iter += pattern_len;
+		}
+		else
+		{
+			if (!pkgconf_buffer_push_byte(dest, *iter++))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans)
+ *
+ *    Copy *src* into *dest*, inserting a backslash before any byte that falls
+ *    within the provided character spans.
+ *    The source and destination buffers must not overlap.
+ *
+ *    :param pkgconf_buffer_t *dest: The destination buffer.
+ *    :param pkgconf_buffer_t *src: The source buffer.
+ *    :param pkgconf_span_t *spans: Array of character spans to escape.
+ *    :param size_t nspans: Number of entries in the *spans* array.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans)
+{
+	pkgconf_charset_t charset;
+
+	pkgconf_charset_from_spans(&charset, spans, nspans);
+
+	return pkgconf_buffer_escape_charset(dest, src, &charset);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans)
+ *
+ *    Build a byte-set from a list of character spans, so that membership tests over it are a
+ *    single lookup.  Callers which test many bytes against the same spans should build the set
+ *    once and pass it to :c:func:`pkgconf_buffer_escape_charset`.
+ *
+ *    :param pkgconf_charset_t *charset: The byte-set to populate.
+ *    :param pkgconf_span_t *spans: Array of character spans to include.
+ *    :param size_t nspans: Number of entries in the *spans* array.
+ *    :return: nothing
+ */
+void
+pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans)
+{
+	memset(charset, 0, sizeof(*charset));
+
+	for (size_t i = 0; i < nspans; i++)
+	{
+		unsigned int lo = spans[i].lo, hi = spans[i].hi;
+
+		if (lo > hi)
+			continue;
+
+		size_t wlo = lo >> 6, whi = hi >> 6;
+
+		/* bits at or above lo, and bits at or below hi, within their words */
+		uint64_t mlo = ~UINT64_C(0) << (lo & 63);
+		uint64_t mhi = (hi & 63) == 63 ? ~UINT64_C(0) : (UINT64_C(1) << ((hi & 63) + 1)) - 1;
+
+		if (wlo == whi)
+		{
+			charset->words[wlo] |= mlo & mhi;
+			continue;
+		}
+
+		charset->words[wlo] |= mlo;
+
+		for (size_t w = wlo + 1; w < whi; w++)
+			charset->words[w] = ~UINT64_C(0);
+
+		charset->words[whi] |= mhi;
+	}
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset)
+ *
+ *    Copy *src* into *dest*, inserting a backslash before any byte which is a member of
+ *    *charset*.  Like :c:func:`pkgconf_buffer_escape`, but takes a prebuilt byte-set.
+ *    The source and destination buffers must not overlap.
+ *
+ *    :param pkgconf_buffer_t *dest: The destination buffer.
+ *    :param pkgconf_buffer_t *src: The source buffer.
+ *    :param pkgconf_charset_t *charset: The set of bytes to escape.
+ *    :return: :code:`true` on success, :code:`false` on allocation failure.
+ */
+bool
+pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset)
+{
+	const char *p = pkgconf_buffer_str(src);
+	const char *run = p;
+
+	if (dest == src ||
+		buffer_storage_overlaps(dest, src->base, pkgconf_buffer_len(src) + 1))
+		return false;
+
+	if (!pkgconf_buffer_len(src))
+		return true;
+
+	for (; *p; p++)
+	{
+		if (!pkgconf_charset_contains(charset, (unsigned char) *p))
+			continue;
+
+		if (p > run && !pkgconf_buffer_append_slice(dest, run, (size_t) (p - run)))
+			return false;
+
+		if (!pkgconf_buffer_push_byte(dest, '\\'))
+			return false;
+
+		run = p;
+	}
+
+	if (p > run)
+		return pkgconf_buffer_append_slice(dest, run, (size_t) (p - run));
+
+	return true;
 }
Index: libpkgconf/bufferset.c
===================================================================
RCS file: libpkgconf/bufferset.c
diff -N libpkgconf/bufferset.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/bufferset.c	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,65 @@
+/*
+ * bufferset.c
+ * dynamically-managed buffer sets
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 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/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `bufferset` module
+ * =============================
+ *
+ * The libpkgconf `bufferset` module contains the functions related to managing
+ * dynamically-allocated sets of buffers.
+ */
+
+pkgconf_bufferset_t *
+pkgconf_bufferset_extend(pkgconf_list_t *list, pkgconf_buffer_t *buffer)
+{
+	pkgconf_bufferset_t *set = calloc(1, sizeof(*set));
+	if (set == NULL)
+		return NULL;
+
+	if (pkgconf_buffer_len(buffer))
+	{
+		if (!pkgconf_buffer_append(&set->buffer, pkgconf_buffer_str(buffer)))
+		{
+			free(set);
+			return NULL;
+		}
+	}
+
+	pkgconf_node_insert_tail(&set->node, set, list);
+	return set;
+}
+
+void
+pkgconf_bufferset_free(pkgconf_list_t *list)
+{
+	pkgconf_node_t *iter, *iter_next;
+
+	PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, iter_next, iter)
+	{
+		pkgconf_bufferset_t *set = iter->data;
+
+		pkgconf_buffer_finalize(&set->buffer);
+		pkgconf_node_delete(&set->node, list);
+
+		free(set);
+	}
+}
Index: libpkgconf/bytecode.c
===================================================================
RCS file: libpkgconf/bytecode.c
diff -N libpkgconf/bytecode.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/bytecode.c	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,553 @@
+/*
+ * bytecode.c
+ * variable expansion bytecode evaluator
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 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/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `bytecode` module
+ * ============================
+ *
+ * The libpkgconf `bytecode` module contains the functions related to
+ * evaluating variable expansion bytecode.
+ */
+
+#define PKGCONF_EVAL_MAX_OUTPUT (PKGCONF_BUFSIZE - 1)
+#define PKGCONF_EVAL_MAX_ITERATIONS (512)
+
+static bool
+pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *p, size_t n)
+{
+	size_t cur = pkgconf_buffer_len(out);
+	if (cur >= PKGCONF_EVAL_MAX_OUTPUT)
+		return false;
+
+	if (n > PKGCONF_EVAL_MAX_OUTPUT - cur)
+	{
+		pkgconf_warn(ctx->client, "warning: truncating very long variable to 64KB\n");
+
+		n = PKGCONF_EVAL_MAX_OUTPUT - cur;
+		if (!pkgconf_buffer_append_slice(out, p, n))
+			pkgconf_error(ctx->client, "pkgconf_bytecode_eval_append_slice: failed to append to slice");
+
+		return false;
+	}
+
+	return pkgconf_buffer_append_slice(out, p, n);
+}
+
+static bool
+pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *s)
+{
+	if (s == NULL || *s == '\0')
+		return true;
+
+	return pkgconf_bytecode_eval_append_slice(ctx, out, s, strlen(s));
+}
+
+static bool
+pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot);
+
+static bool
+pkgconf_bytecode_read_op(const uint8_t *p, size_t remaining,
+	const pkgconf_bytecode_op_t **op, size_t *op_size)
+{
+	if (p == NULL || remaining < sizeof(**op))
+		return false;
+
+	*op = (const pkgconf_bytecode_op_t *) p;
+	if ((size_t) (*op)->size > remaining - sizeof(**op))
+		return false;
+
+	*op_size = sizeof(**op) + (size_t) (*op)->size;
+	return true;
+}
+
+static pkgconf_variable_t *
+pkgconf_bytecode_eval_scan(const pkgconf_list_t *vars, const char *name, size_t nlen, unsigned int require_flags, unsigned int forbid_flags)
+{
+	const pkgconf_node_t *node;
+
+	PKGCONF_FOREACH_LIST_ENTRY(vars->head, node)
+	{
+		pkgconf_variable_t *v = node->data;
+
+		if ((v->flags & require_flags) != require_flags)
+			continue;
+
+		if ((v->flags & forbid_flags) != 0)
+			continue;
+
+		if (pkgconf_str_eq_slice(v->key, name, nlen))
+			return v;
+	}
+
+	return NULL;
+}
+
+pkgconf_variable_t *
+pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen)
+{
+	pkgconf_variable_t *v;
+
+	if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, PKGCONF_VARIABLEF_OVERRIDE, 0)) != NULL)
+		return v;
+
+	if (ctx->vars != NULL && (v = pkgconf_bytecode_eval_scan(ctx->vars, name, nlen, 0, 0)) != NULL)
+		return v;
+
+	if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, 0, PKGCONF_VARIABLEF_OVERRIDE)) != NULL)
+		return v;
+
+	return NULL;
+}
+
+static bool
+pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+	pkgconf_variable_t *v;
+
+	v = pkgconf_bytecode_eval_lookup_var(ctx, name, nlen);
+	if (v == NULL)
+		return true;
+
+	if (v->expanding)
+		return false;
+
+	v->expanding = true;
+
+	bool inner_saw = false;
+	bool ok = pkgconf_bytecode_eval_internal(ctx, &v->bc, out, &inner_saw);
+
+	v->expanding = false;
+
+	if (!ok)
+		return false;
+
+	if (saw_sysroot != NULL)
+		*saw_sysroot |= inner_saw;
+
+	return true;
+}
+
+static bool
+pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+	(void) ctx;
+
+	if (bc == NULL || out == NULL)
+		return false;
+
+	if (bc->len != 0 && bc->base == NULL)
+		return false;
+
+	const uint8_t *p = bc->base;
+	size_t remaining = bc->len;
+
+	if (++ctx->expansions > PKGCONF_EVAL_MAX_ITERATIONS)
+	{
+		pkgconf_warn(ctx->client,
+			"warning: bytecode program exceeds iteration limit (" SIZE_FMT_SPECIFIER ")\n",
+			ctx->expansions - 1);
+		return false;
+	}
+
+	while (remaining != 0)
+	{
+		const pkgconf_bytecode_op_t *op;
+		size_t op_size;
+
+		if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
+			return false;
+
+		switch (op->tag)
+		{
+		case PKGCONF_BYTECODE_OP_TEXT:
+			/* this only fails due to truncation */
+			if (!pkgconf_bytecode_eval_append_slice(ctx, out, op->data, op->size))
+				return false;
+			break;
+
+		case PKGCONF_BYTECODE_OP_VAR:
+			if (!pkgconf_bytecode_eval_var(ctx, op->data, op->size, out, saw_sysroot))
+				return false;
+			break;
+
+		case PKGCONF_BYTECODE_OP_SYSROOT:
+			if (saw_sysroot != NULL)
+				*saw_sysroot = true;
+			if (!pkgconf_bytecode_eval_append(ctx, out, pkgconf_buffer_str_or_empty(&ctx->sysroot)))
+				return false;
+			break;
+
+		default:
+			/* reserved/unimplemented */
+			return false;
+		}
+
+		p += op_size;
+		remaining -= op_size;
+	}
+
+	return true;
+}
+
+static bool
+pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_client_t *client, const pkgconf_list_t *vars)
+{
+	memset(ctx, 0, sizeof(*ctx));
+
+	ctx->client = client;
+	ctx->vars = vars;
+
+	const char *raw = pkgconf_client_get_sysroot_dir(client);
+
+	/* disabled sysroot cases */
+	if (raw == NULL || *raw == '\0')
+		return true;
+
+	if (raw[0] == '.' && raw[1] == '\0')
+		return true;
+
+	if (raw[0] == '/' && raw[1] == '\0')
+		return true;
+
+	if (!pkgconf_buffer_append(&ctx->sysroot, raw))
+		return false;
+
+	while (pkgconf_buffer_len(&ctx->sysroot) > 1 && ctx->sysroot.end[-1] == '/')
+	{
+		if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
+			return false;
+	}
+
+	/* if normalization yields "/", disable by making buffer empty */
+	if (pkgconf_buffer_len(&ctx->sysroot) == 1 && ctx->sysroot.base[0] == '/')
+	{
+		if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
+			return false;
+	}
+
+	return true;
+}
+
+bool
+pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *vars, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
+{
+	bool ret;
+
+	if (client == NULL || bc == NULL || out == NULL)
+		return false;
+
+	pkgconf_bytecode_eval_ctx_t ctx;
+	if (!pkgconf_bytecode_eval_ctx_init(&ctx, client, vars))
+		return false;
+
+	if (saw_sysroot != NULL)
+		*saw_sysroot = false;
+
+	ret = pkgconf_bytecode_eval_internal(&ctx, bc, out, saw_sysroot);
+
+	pkgconf_buffer_finalize(&ctx.sysroot);
+
+	return ret;
+}
+
+bool
+pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size)
+{
+	pkgconf_bytecode_op_t op = {
+		.tag = tag,
+		.size = size,
+	};
+
+	if (!pkgconf_buffer_append_slice(buf, (const char *) &op, sizeof(op)))
+		return false;
+
+	if (size != 0)
+	{
+		if (!pkgconf_buffer_append_slice(buf, (const char *) data, (size_t) size))
+			return false;
+	}
+
+	return true;
+}
+
+bool
+pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n)
+{
+	if (p == NULL || n == 0)
+		return true;
+
+	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_TEXT, p, (uint32_t) n);
+}
+
+bool
+pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen)
+{
+	if (name == NULL || nlen == 0)
+		return true;
+
+	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_VAR, name, (uint32_t) nlen);
+}
+
+bool
+pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf)
+{
+	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_SYSROOT, NULL, 0);
+}
+
+void
+pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf)
+{
+	bc->base = (const uint8_t *)buf->base;
+	bc->len = buf->base != NULL ? (size_t)(buf->end - buf->base) : 0;
+}
+
+bool
+pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value)
+{
+	const char *p, *text_start;
+
+	if (out == NULL || value == NULL)
+		return false;
+
+	p = value;
+	text_start = value;
+
+	for (; *p != '\0'; p++)
+	{
+		const char *name, *q;
+
+		if (*p != '$')
+			continue;
+
+		/* $$ escapes to a literal $ */
+		if (p[1] == '$')
+		{
+			if (p > text_start)
+			{
+				if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+					return false;
+			}
+
+			if (!pkgconf_bytecode_emit_text(out, "$", 1))
+				return false;
+
+			p++;
+			text_start = p + 1;
+			continue;
+		}
+
+		if (p[1] != '{')
+			continue;
+
+		if (p > text_start)
+		{
+			if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+				return false;
+		}
+
+		name = p + 2;
+		q = name;
+
+		for (; *q != '\0' && *q != '}'; q++)
+			;
+
+		/* make sure a variable expansion ends with } */
+		if (*q != '}')
+		{
+			text_start = p;
+			continue;
+		}
+
+		/* if this is not a valid variable, emit it as text */
+		size_t nlen = (size_t)(q - name);
+		if (nlen == 0 || nlen >= PKGCONF_ITEM_SIZE)
+		{
+			if (!pkgconf_bytecode_emit_text(out, p, (size_t)((q + 1) - p)))
+				return false;
+
+			p = q;
+			text_start = p + 1;
+			continue;
+		}
+
+		/* we need to special-case ${pc_sysrootdir} and emit OP_SYSROOT instead... */
+		if (nlen == strlen("pc_sysrootdir") && !memcmp(name, "pc_sysrootdir", nlen))
+		{
+			if (!pkgconf_bytecode_emit_sysroot(out))
+				return false;
+		}
+		else
+		{
+			if (!pkgconf_bytecode_emit_var(out, name, nlen))
+				return false;
+		}
+
+		p = q;
+		text_start = p + 1;
+	}
+
+	if (p > text_start)
+	{
+		if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
+			return false;
+	}
+
+	return true;
+}
+
+bool
+pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out)
+{
+	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_bytecode_t bc;
+	bool ret = false;
+
+	if (!pkgconf_bytecode_compile(&bcbuf, input))
+	{
+		pkgconf_buffer_finalize(&bcbuf);
+		return false;
+	}
+
+	pkgconf_bytecode_from_buffer(&bc, &bcbuf);
+
+	ret = pkgconf_bytecode_eval(client, vars, &bc, out, saw_sysroot);
+
+	pkgconf_buffer_finalize(&bcbuf);
+
+	return ret;
+}
+
+char *
+pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot)
+{
+	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!pkgconf_bytecode_eval_str_to_buf(client, vars, input, saw_sysroot, &out))
+	{
+		if (pkgconf_buffer_len(&out) > 0)
+			return pkgconf_buffer_freeze(&out);
+
+		pkgconf_buffer_finalize(&out);
+		return NULL;
+	}
+
+	if (pkgconf_buffer_len(&out) == 0)
+	{
+		pkgconf_buffer_finalize(&out);
+		return strdup("");
+	}
+
+	return pkgconf_buffer_freeze(&out);
+}
+
+bool
+pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key)
+{
+	const uint8_t *p;
+	size_t remaining;
+	size_t klen;
+
+	if (buf == NULL || key == NULL)
+		return false;
+
+	klen = strlen(key);
+	p = (const uint8_t *) buf->base;
+	remaining = pkgconf_buffer_len(buf);
+
+	while (remaining != 0)
+	{
+		const pkgconf_bytecode_op_t *op;
+		size_t op_size;
+
+		if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
+			return false;
+
+		if (op->tag == PKGCONF_BYTECODE_OP_VAR)
+		{
+			if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
+				return true;
+		}
+
+		p += op_size;
+		remaining -= op_size;
+	}
+
+	return false;
+}
+
+static bool
+pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t *op, const char *key)
+{
+	const size_t klen = strlen(key);
+
+	if (op->tag != PKGCONF_BYTECODE_OP_VAR)
+		return false;
+
+	if (op->size != (uint32_t) klen)
+		return false;
+
+	return memcmp(op->data, key, klen) == 0;
+}
+
+static bool
+pkgconf_bytecode_append_stream(pkgconf_buffer_t *dst, const pkgconf_buffer_t *bcbuf)
+{
+	if (dst == NULL || bcbuf == NULL || pkgconf_buffer_str(bcbuf) == NULL)
+		return true;
+
+	return pkgconf_buffer_append_slice(dst, pkgconf_buffer_str(bcbuf), pkgconf_buffer_len(bcbuf));
+}
+
+bool
+pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev)
+{
+	if (out == NULL || rhs == NULL || key == NULL || prev == NULL)
+		return false;
+
+	const uint8_t *p = (const uint8_t *) rhs->base;
+	size_t remaining = pkgconf_buffer_len(rhs);
+
+	while (remaining != 0)
+	{
+		const pkgconf_bytecode_op_t *op;
+		size_t op_size;
+
+		if (!pkgconf_bytecode_read_op(p, remaining, &op, &op_size))
+			return false;
+
+		if (pkgconf_bytecode_op_is_selfref(op, key))
+		{
+			if (!pkgconf_bytecode_append_stream(out, prev))
+				return false;
+		}
+		else
+		{
+			if (!pkgconf_buffer_append_slice(out, (const char *) op, op_size))
+				return false;
+		}
+
+		p += op_size;
+		remaining -= op_size;
+	}
+
+	return true;
+}
Index: libpkgconf/cache.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/cache.c,v
diff -u -p -r1.1.1.1 cache.c
--- libpkgconf/cache.c	3 May 2025 15:09:38 -0000	1.1.1.1
+++ libpkgconf/cache.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * cache.c
  * package object cache
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2013 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -40,37 +42,6 @@ cache_member_cmp(const void *a, const vo
 	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
  *
@@ -125,20 +96,51 @@ pkgconf_cache_add(pkgconf_client_t *clie
 	if (pkg == NULL)
 		return;
 
+	pkgconf_pkg_t *cached_pkg = pkgconf_cache_lookup(client, pkg->id);
+	if (cached_pkg != NULL)
+	{
+		pkgconf_pkg_unref(client, cached_pkg);
+		return;
+	}
+
 	pkgconf_pkg_ref(client, pkg);
 
-	PKGCONF_TRACE(client, "added @%p to cache", pkg);
+	pkgconf_pkg_t **new_table;
 
 	/* mark package as cached */
 	pkg->flags |= PKGCONF_PKG_PROPF_CACHED;
 
 	++client->cache_count;
-	client->cache_table = pkgconf_reallocarray(client->cache_table,
+	new_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);
+	/* if we are out of memory, roll back adding to cache and bail */
+	if (new_table == NULL)
+	{
+		--client->cache_count;
+		pkg->flags &= ~PKGCONF_PKG_PROPF_CACHED;
+		pkgconf_pkg_unref(client, pkg);
+		return;
+	}
+
+	client->cache_table = new_table;
+
+	size_t lo = 0, hi = client->cache_count - 1;
+	while (lo < hi)
+	{
+		size_t mid = lo + (hi - lo) / 2;
+
+		if (strcmp(client->cache_table[mid]->id, pkg->id) < 0)
+			lo = mid + 1;
+		else
+			hi = mid;
+	}
+
+	memmove(&client->cache_table[lo + 1], &client->cache_table[lo],
+		(client->cache_count - 1 - lo) * sizeof(void *));
+	client->cache_table[lo] = pkg;
+
+	PKGCONF_TRACE(client, "added @%p to cache", pkg);
 }
 
 /*
@@ -177,24 +179,19 @@ pkgconf_cache_remove(pkgconf_client_t *c
 
 	(*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();
-	}
+	size_t idx = (size_t)(slot - client->cache_table);
+	memmove(&client->cache_table[idx], &client->cache_table[idx + 1],
+		(client->cache_count - idx - 1) * sizeof(void *));
 
 	client->cache_count--;
 	if (client->cache_count > 0)
 	{
-		client->cache_table = pkgconf_reallocarray(client->cache_table,
+		pkgconf_pkg_t **new_table = pkgconf_reallocarray(client->cache_table,
 			client->cache_count, sizeof(void *));
+
+		if (new_table != NULL)
+			client->cache_table = new_table;
 	}
 	else
 	{
Index: libpkgconf/client.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/client.c,v
diff -u -p -r1.1.1.1 client.c
--- libpkgconf/client.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/client.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * client.c
  * libpkgconf consumer lifecycle management
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2016 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -60,17 +62,26 @@ trace_path_list(const pkgconf_client_t *
 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);
+	pkgconf_path_build_from_environ(client, "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;
 
-		if (getenv("PKG_CONFIG_LIBDIR") != NULL)
+#ifdef _WIN32
+		/*
+		 * NOTE: setting the pkgconf path from the registry is deprecated
+		 * and will be removed in pkgconf 3.1.
+		 */
+		(void) pkgconf_path_build_from_registry(client, HKEY_CURRENT_USER, &client->dir_list, true);
+		(void) pkgconf_path_build_from_registry(client, HKEY_LOCAL_MACHINE, &client->dir_list, true);
+#endif
+
+		if (pkgconf_client_getenv(client, "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);
+			(void) pkgconf_path_build_from_environ(client, "PKG_CONFIG_LIBDIR", NULL, &dir_list, true);
 			prepend_list = &dir_list;
 		}
 
@@ -82,7 +93,7 @@ pkgconf_client_dir_list_build(pkgconf_cl
 /*
  * !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)
+ * .. 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, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler)
  *
  *    Initialise a pkgconf client object.
  *
@@ -90,13 +101,45 @@ pkgconf_client_dir_list_build(pkgconf_cl
  *    :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
+ *    :param void* client_data: user data associated with the client
+ *    :param pkgconf_environ_lookup_handler_func_t environ_lookup_handler: the lookup handler to use for environment variables
  *    :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)
+pkgconf_client_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler)
 {
-	client->error_handler_data = error_handler_data;
-	client->error_handler = error_handler;
+	pkgconf_client_options_t options = {
+		.error_handler = error_handler,
+		.error_handler_data = error_handler_data,
+		.personality = personality,
+		.client_data = client_data,
+		.environ_lookup_handler = environ_lookup_handler,
+	};
+
+	pkgconf_client_init_with_options(client, &options);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options)
+ *
+ *    Initialise a pkgconf client object using an options structure.
+ *
+ *    :param pkgconf_client_t* client: The client to initialise.
+ *    :param pkgconf_client_options_t* options: The options used to configure the client.
+ *    :return: nothing
+ */
+void
+pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options)
+{
+	const pkgconf_cross_personality_t *personality = options->personality;
+
+	client->personality = personality;
+	client->client_data = options->client_data;
+	client->environ_lookup_handler = options->environ_lookup_handler;
+	client->error_handler_data = options->error_handler_data;
+	client->error_handler = options->error_handler;
 	client->auditf = NULL;
 	client->cache_table = NULL;
 	client->cache_count = 0;
@@ -106,43 +149,50 @@ pkgconf_client_init(pkgconf_client_t *cl
 		pkgconf_client_set_trace_handler(client, NULL, NULL);
 #endif
 
-	pkgconf_client_set_error_handler(client, error_handler, error_handler_data);
+	if (options->unveil_handler != NULL)
+		pkgconf_client_set_unveil_handler(client, options->unveil_handler);
+	else if (client->unveil_handler == NULL)
+		pkgconf_client_set_unveil_handler(client, NULL);
+
+	pkgconf_client_set_error_handler(client, options->error_handler, options->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)
+	if(pkgconf_client_getenv(client, "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);
+		pkgconf_path_build_from_environ(client, "PKG_CONFIG_SYSTEM_LIBRARY_PATH", NULL, &client->filter_libdirs, false);
 
-	if(getenv("PKG_CONFIG_SYSTEM_INCLUDE_PATH") == NULL)
+	if(pkgconf_client_getenv(client, "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);
+		pkgconf_path_build_from_environ(client, "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);
+	pkgconf_path_build_from_environ(client, "BELIBRARIES", NULL, &client->filter_libdirs, false);
 #else
-	pkgconf_path_build_from_environ("LIBRARY_PATH", NULL, &client->filter_libdirs, false);
+	pkgconf_path_build_from_environ(client, "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);
+	pkgconf_path_build_from_environ(client, "CPATH", NULL, &client->filter_includedirs, false);
+	pkgconf_path_build_from_environ(client, "C_INCLUDE_PATH", NULL, &client->filter_includedirs, false);
+	pkgconf_path_build_from_environ(client, "CPLUS_INCLUDE_PATH", NULL, &client->filter_includedirs, false);
+	pkgconf_path_build_from_environ(client, "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);
+	pkgconf_path_build_from_environ(client, "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);
+
+	client->output = pkgconf_output_default();
 }
 
 /*
@@ -155,17 +205,59 @@ pkgconf_client_init(pkgconf_client_t *cl
  *    :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
+ *    :param void* client_data: user data associated with the client
+ *    :param pkgconf_environ_lookup_handler_func_t environ_lookup_handler: the lookup handler to use for environment variables
  *    :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_new(pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler)
+{
+	pkgconf_client_options_t options = {
+		.error_handler = error_handler,
+		.error_handler_data = error_handler_data,
+		.personality = personality,
+		.client_data = client_data,
+		.environ_lookup_handler = environ_lookup_handler,
+	};
+
+	return pkgconf_client_new_with_options(&options);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: pkgconf_client_t* pkgconf_client_new_with_options(const pkgconf_client_options_t *options)
+ *
+ *    Allocate and initialise a pkgconf client object using an options structure.
+ *
+ *    :param pkgconf_client_options_t* options: The options used to configure the client.
+ *    :return: A pkgconf client object.
+ *    :rtype: pkgconf_client_t*
+ */
+pkgconf_client_t *
+pkgconf_client_new_with_options(const pkgconf_client_options_t *options)
 {
 	pkgconf_client_t *out = calloc(1, sizeof(pkgconf_client_t));
-	pkgconf_client_init(out, error_handler, error_handler_data, personality);
+	if (out == NULL)
+		return NULL;
+
+	pkgconf_client_init_with_options(out, options);
 	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, tn, n)
+	{
+		pkgconf_pkg_t *pkg = n->data;
+		pkgconf_pkg_unref(client, pkg);
+	}
+}
+
 /*
  * !doc
  *
@@ -181,6 +273,8 @@ pkgconf_client_deinit(pkgconf_client_t *
 {
 	PKGCONF_TRACE(client, "deinit @%p", client);
 
+	unref_preload_list(client);
+
 	if (client->prefix_varname != NULL)
 		free(client->prefix_varname);
 
@@ -196,6 +290,10 @@ pkgconf_client_deinit(pkgconf_client_t *
 	pkgconf_tuple_free_global(client);
 	pkgconf_path_free(&client->dir_list);
 	pkgconf_cache_free(client);
+
+	pkgconf_buffer_finalize(&client->_scratch_buffer);
+
+	memset(client, '\0', sizeof(*client));
 }
 
 /*
@@ -249,14 +347,25 @@ pkgconf_client_get_sysroot_dir(const pkg
 void
 pkgconf_client_set_sysroot_dir(pkgconf_client_t *client, const char *sysroot_dir)
 {
+	if (sysroot_dir != NULL && (!strcmp(sysroot_dir, "/") || !strcmp(sysroot_dir, ".")))
+	{
+		pkgconf_warn(client, "ignoring bogus sysroot_dir: %s", sysroot_dir);
+		sysroot_dir = NULL;
+	}
+
 	if (client->sysroot_dir != NULL)
 		free(client->sysroot_dir);
 
 	client->sysroot_dir = sysroot_dir != NULL ? strdup(sysroot_dir) : NULL;
 
+	/* normalize separators so the sysroot compares equal to the (already
+	 * canonicalized) paths it is matched against during injection */
+	if (client->sysroot_dir != NULL)
+		pkgconf_path_normalize_separators(client->sysroot_dir);
+
 	PKGCONF_TRACE(client, "set sysroot_dir to: %s", client->sysroot_dir != NULL ? client->sysroot_dir : "<default>");
 
-	pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : "/");
+	pkgconf_tuple_add_global(client, "pc_sysrootdir", client->sysroot_dir != NULL ? client->sysroot_dir : "");
 }
 
 /*
@@ -298,6 +407,9 @@ pkgconf_client_set_buildroot_dir(pkgconf
 
 	client->buildroot_dir = buildroot_dir != NULL ? strdup(buildroot_dir) : NULL;
 
+	if (client->buildroot_dir != NULL)
+		pkgconf_path_normalize_separators(client->buildroot_dir);
+
 	PKGCONF_TRACE(client, "set buildroot_dir to: %s", client->buildroot_dir != NULL ? client->buildroot_dir : "<default>");
 
 	pkgconf_tuple_add_global(client, "pc_top_builddir", client->buildroot_dir != NULL ? client->buildroot_dir : "$(top_builddir)");
@@ -318,14 +430,32 @@ pkgconf_client_set_buildroot_dir(pkgconf
 bool
 pkgconf_error(const pkgconf_client_t *client, const char *format, ...)
 {
-	char errbuf[PKGCONF_BUFSIZE];
+	char *errbuf;
+	ssize_t msgsize = 0;
+	bool ret;
 	va_list va;
 
 	va_start(va, format);
-	vsnprintf(errbuf, sizeof errbuf, format, va);
+	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);
 
-	return client->error_handler(errbuf, client, client->error_handler_data);
+	ret = client->error_handler(errbuf, client, client->error_handler_data);
+	free(errbuf);
+
+	return ret;
 }
 
 /*
@@ -343,14 +473,32 @@ pkgconf_error(const pkgconf_client_t *cl
 bool
 pkgconf_warn(const pkgconf_client_t *client, const char *format, ...)
 {
-	char errbuf[PKGCONF_BUFSIZE];
+	char *errbuf;
+	ssize_t msgsize = 0;
+	bool ret;
 	va_list va;
 
 	va_start(va, format);
-	vsnprintf(errbuf, sizeof errbuf, format, va);
+	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);
 
-	return client->warn_handler(errbuf, client, client->warn_handler_data);
+	ret = client->warn_handler(errbuf, client, client->warn_handler_data);
+	free(errbuf);
+
+	return ret;
 }
 
 /*
@@ -371,22 +519,56 @@ pkgconf_warn(const pkgconf_client_t *cli
 bool
 pkgconf_trace(const pkgconf_client_t *client, const char *filename, size_t lineno, const char *funcname, const char *format, ...)
 {
-	char errbuf[PKGCONF_BUFSIZE];
-	size_t len;
+	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;
 
-	len = snprintf(errbuf, sizeof errbuf, "%s:" SIZE_FMT_SPECIFIER " [%s]: ", filename, lineno, funcname);
+	snprintf(prefix, sizeof prefix, "%s:" SIZE_FMT_SPECIFIER " [%s]:", filename, lineno, funcname);
 
 	va_start(va, format);
-	vsnprintf(errbuf + len, sizeof(errbuf) - len, format, va);
+	errlen = vsnprintf(NULL, 0, format, va);
 	va_end(va);
 
-	pkgconf_strlcat(errbuf, "\n", sizeof errbuf);
+	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);
 
-	return client->trace_handler(errbuf, client, client->trace_handler_data);
+	finallen = snprintf(NULL, 0, "%s %s\n", prefix, errbuf);
+	if (finallen < 0)
+	{
+		free(errbuf);
+		return false;
+	}
+
+	finallen++;
+	finalbuf = calloc(1, finallen);
+	if (finalbuf == NULL)
+	{
+		free(errbuf);
+		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;
 }
 
 /*
@@ -412,6 +594,14 @@ pkgconf_default_error_handler(const char
 	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
  *
@@ -571,6 +761,47 @@ pkgconf_client_set_error_handler(pkgconf
 	}
 }
 
+/*
+ * !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;
+	}
+	else
+		PKGCONF_TRACE(client, "installing custom unveil handler");
+}
+
 #ifndef PKGCONF_LITE
 /*
  * !doc
@@ -605,11 +836,136 @@ pkgconf_client_set_trace_handler(pkgconf
 {
 	client->trace_handler = trace_handler;
 	client->trace_handler_data = trace_handler_data;
+}
+#endif
 
-	if (client->trace_handler == NULL)
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_client_preload_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+ *
+ *    Adds a package to the preloaded packages set.
+ *
+ *    :param pkgconf_client_t* client: The client object for preloading.
+ *    :param pkgconf_pkg_t* pkg: The package to preload.
+ *    :return: true on success, false on error
+ *    :rtype: bool
+ */
+bool
+pkgconf_client_preload_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg)
+{
+	PKGCONF_TRACE(client, "preloading pkg %s@%p", pkg->id, pkg);
+
+	pkg->flags |= PKGCONF_PKG_PROPF_PRELOADED;
+
+	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_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;
+
+	bool ret = pkgconf_client_preload_one(client, pkg);
+	pkgconf_pkg_unref(client, pkg);
+
+	return ret;
+}
+
+/*
+ * !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 = true;
+
+	data = pkgconf_client_getenv(client, env);
+	if (data == NULL)
+		return true;
+
+	pkgconf_path_split(data, &pathlist, true);
+
+	PKGCONF_FOREACH_LIST_ENTRY(pathlist.head, n)
 	{
-		client->trace_handler = pkgconf_default_error_handler;
-		PKGCONF_TRACE(client, "installing default trace handler");
+		pkgconf_path_t *pn = n->data;
+
+		ret = pkgconf_client_preload_path(client, pn->path);
+		if (!ret)
+			break;
 	}
+
+	pkgconf_path_free(&pathlist);
+
+	return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output)
+ *
+ *    Sets the client's output object.  This is mainly a convenience function for clients
+ *    to use.
+ *
+ *    :param pkgconf_client_t* client: The client object to set the output object for.
+ *    :param pkgconf_output_t* output: The output object to use.
+ *    :return: nothing
+ */
+void
+pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output)
+{
+	client->output = output;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_client_getenv(const pkgconf_client_t *client, const char *key)
+ *
+ *    Looks up an environmental variable which may be mocked, otherwise fetches
+ *    from the main environment.
+ *
+ *    :param pkgconf_client_t* client: the client object to use for looking up environmental variables.
+ *    :param char* key: the environmental variable to look up.
+ *    :return: the environmental variable contents else NULL
+ *    :rtype: const char*
+ */
+const char *
+pkgconf_client_getenv(const pkgconf_client_t *client, const char *key)
+{
+	if (client != NULL && client->environ_lookup_handler != NULL)
+		return client->environ_lookup_handler(client, key);
+
+	return getenv(key);
 }
-#endif
Index: libpkgconf/config.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/config.h,v
diff -u -p -r1.3 config.h
--- libpkgconf/config.h	6 May 2026 00:07:46 -0000	1.3
+++ libpkgconf/config.h	4 Aug 2026 16:46:06 -0000
@@ -1,6 +1,6 @@
 #define PACKAGE_NAME "pkgconf-lite"
-#define PACKAGE_BUGREPORT "https://git.dereferenced.org/pkgconf/pkgconf/issues"
-#define PACKAGE_VERSION "2.4.3"
+#define PACKAGE_BUGREPORT "https://github.com/pkgconf/pkgconf/issues"
+#define PACKAGE_VERSION "3.0.5"
 #define PACKAGE PACKAGE_NAME " " PACKAGE_VERSION
 #define HAVE_DECL_STRLCPY 1
 #define HAVE_STRLCAT
@@ -8,4 +8,5 @@
 #define HAVE_DECL_STRNDUP 1
 #define HAVE_DECL_PLEDGE 1
 #define HAVE_DECL_UNVEIL 1
-#define HAVE_DECL_REALLOCARRAY 1
+#define HAVE_DECL_PLEDGE 1
+#define HAVE_DECL_UNVEIL 1
Index: libpkgconf/dependency.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/dependency.c,v
diff -u -p -r1.1.1.1 dependency.c
--- libpkgconf/dependency.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/dependency.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * dependency.c
  * dependency parsing and management
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -37,19 +39,18 @@ typedef enum {
 
 #define DEBUG_PARSE 0
 
-static const char *
-dependency_to_str(const pkgconf_dependency_t *dep, char *buf, size_t buflen)
+static inline const char *
+dependency_to_buf(const pkgconf_dependency_t *dep, pkgconf_buffer_t *buf)
 {
-	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);
-	}
+	pkgconf_buffer_reset(buf);
+	if (!pkgconf_buffer_append(buf, dep->package))
+		return NULL;
+
+	if (dep->version != NULL &&
+		!pkgconf_buffer_append_fmt(buf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version))
+		return NULL;
 
-	return buf;
+	return pkgconf_buffer_str(buf);
 }
 
 /* find a colliding dependency that is coloured differently */
@@ -72,32 +73,52 @@ find_colliding_dependency(const pkgconf_
 	return NULL;
 }
 
+static inline const char *
+dependency_trace_str(const pkgconf_client_t *client, const pkgconf_dependency_t *dep, pkgconf_buffer_t *buf)
+{
+#ifndef PKGCONF_LITE
+	const char *str;
+
+	if (pkgconf_client_get_trace_handler(client) == NULL)
+		return dep->package;
+
+	str = dependency_to_buf(dep, buf);
+	return str != NULL ? str : dep->package;
+#else
+	return dep->package;
+#endif
+}
+
 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_buffer_t depbuf = PKGCONF_BUFFER_INITIALIZER;
 	pkgconf_dependency_t *dep2 = find_colliding_dependency(dep, list);
+	const char *depstr = dependency_trace_str(client, dep, &depbuf);
 
 	/* there is already a node in the graph which describes this dependency */
 	if (dep2 != NULL)
 	{
-		char depbuf2[PKGCONF_ITEM_SIZE];
+		pkgconf_buffer_t depbuf2 = PKGCONF_BUFFER_INITIALIZER;
+		const char *depstr2 = dependency_trace_str(client, dep2, &depbuf2);
 
 		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);
+			depstr, dep->flags, depstr2, 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_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr, dep);
 
+			pkgconf_buffer_finalize(&depbuf);
+			pkgconf_buffer_finalize(&depbuf2);
 			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_TRACE(client, "dropping dependency [%s]@%p because of collision", depstr2, dep2);
 
 			pkgconf_node_delete(&dep2->iter, list);
 			pkgconf_dependency_unref(dep2->owner, dep2);
@@ -110,11 +131,15 @@ add_or_replace_dependency_node(pkgconf_c
 			 * fragment deduplication will handle the excessive fragments.
 			 */
 			PKGCONF_TRACE(client, "keeping both dependencies (harmless)");
+
+		pkgconf_buffer_finalize(&depbuf2);
 	}
 
-	PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", dependency_to_str(dep, depbuf, sizeof depbuf), list, dep->flags);
+	PKGCONF_TRACE(client, "added dependency [%s] to list @%p; flags=%x", depstr, list, dep->flags);
 	pkgconf_node_insert_tail(&dep->iter, pkgconf_dependency_ref(dep->owner, dep), list);
 
+	pkgconf_buffer_finalize(&depbuf);
+
 	/* This dependency is intentionally unowned.
 	 *
 	 * Internally we have no use for the returned type, and usually just
@@ -131,10 +156,25 @@ pkgconf_dependency_addraw(pkgconf_client
 	pkgconf_dependency_t *dep;
 
 	dep = calloc(1, sizeof(pkgconf_dependency_t));
+	if (dep == NULL)
+		return NULL;
+
 	dep->package = pkgconf_strndup(package, package_sz);
+	if (dep->package == NULL)
+	{
+		pkgconf_dependency_free_one(dep);
+		return NULL;
+	}
 
 	if (version_sz != 0)
+	{
 		dep->version = pkgconf_strndup(version, version_sz);
+		if (dep->version == NULL)
+		{
+			pkgconf_dependency_free_one(dep);
+			return NULL;
+		}
+	}
 
 	dep->compare = compare;
 	dep->flags = flags;
@@ -166,6 +206,8 @@ pkgconf_dependency_add(pkgconf_client_t 
 	pkgconf_dependency_t *dep;
 	dep = pkgconf_dependency_addraw(client, list, package, strlen(package), version,
 					version != NULL ? strlen(version) : 0, compare, flags);
+	if (dep == NULL)
+		return NULL;
 	return pkgconf_dependency_ref(dep->owner, dep);
 }
 
@@ -208,6 +250,9 @@ pkgconf_dependency_free_one(pkgconf_depe
 	if (dep->version != NULL)
 		free(dep->version);
 
+	if (dep->why != NULL)
+		free(dep->why);
+
 	free(dep);
 }
 
@@ -303,20 +348,25 @@ pkgconf_dependency_parse_str(pkgconf_cli
 {
 	parse_state_t state = OUTSIDE_MODULE;
 	pkgconf_pkg_comparator_t compare = PKGCONF_CMP_ANY;
-	char cmpname[PKGCONF_ITEM_SIZE];
-	char buf[PKGCONF_BUFSIZE];
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t cmpname = PKGCONF_BUFFER_INITIALIZER;
 	size_t package_sz = 0, version_sz = 0;
-	char *start = buf;
-	char *ptr = 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;
+	char *opstart = NULL;
 
-	memset(cmpname, '\0', sizeof cmpname);
+	if (depends == NULL || *depends == '\0')
+		return;
 
-	pkgconf_strlcpy(buf, depends, sizeof buf);
-	pkgconf_strlcat(buf, " ", sizeof buf);
+	if (!pkgconf_buffer_append(&buf, depends))
+		goto out;
+
+	if (!pkgconf_buffer_append(&buf, " "))
+		goto out;
+
+	start = ptr = buf.base;
 
 	while (*ptr)
 	{
@@ -378,23 +428,22 @@ pkgconf_dependency_parse_str(pkgconf_cli
 		case BEFORE_OPERATOR:
 			if (PKGCONF_IS_OPERATOR_CHAR(*ptr))
 			{
+				opstart = 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;
-			}
 
+			pkgconf_buffer_rewind(&cmpname);
+			if (!pkgconf_buffer_append_slice(&cmpname, opstart, ptr - opstart))
+				goto out;
+
+			compare = pkgconf_pkg_comparator_lookup_by_name(pkgconf_buffer_str(&cmpname));
 			state = AFTER_OPERATOR;
-			compare = pkgconf_pkg_comparator_lookup_by_name(cmpname);
 			// fallthrough
 
 		case AFTER_OPERATOR:
@@ -415,9 +464,9 @@ pkgconf_dependency_parse_str(pkgconf_cli
 				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;
+				opstart = NULL;
+				pkgconf_buffer_rewind(&cmpname);
 			}
 
 			if (state == OUTSIDE_MODULE)
@@ -427,6 +476,10 @@ pkgconf_dependency_parse_str(pkgconf_cli
 
 		ptr++;
 	}
+
+out:
+	pkgconf_buffer_finalize(&cmpname);
+	pkgconf_buffer_finalize(&buf);
 }
 
 /*
@@ -448,7 +501,7 @@ pkgconf_dependency_parse_str(pkgconf_cli
 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);
+	char *kvdepends = pkgconf_bytecode_eval_str(client, &pkg->vars, depends, NULL);
 
 	pkgconf_dependency_parse_str(client, deplist, kvdepends, flags);
 	free(kvdepends);
@@ -471,10 +524,25 @@ pkgconf_dependency_copy(pkgconf_client_t
 	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 (new_dep->package == NULL)
+	{
+		pkgconf_dependency_free_one(new_dep);
+		return NULL;
+	}
 
 	if (dep->version != NULL)
+	{
 		new_dep->version = strdup(dep->version);
+		if (new_dep->version == NULL)
+		{
+			pkgconf_dependency_free_one(new_dep);
+			return NULL;
+		}
+	}
 
 	new_dep->compare = dep->compare;
 	new_dep->flags = dep->flags;
Index: libpkgconf/fileio.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/fileio.c,v
diff -u -p -r1.1.1.1 fileio.c
--- libpkgconf/fileio.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/fileio.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * fileio.c
  * File reading utilities
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012, 2025 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -13,75 +15,52 @@
  * from the use of this software.
  */
 
+#include <libpkgconf/config.h>
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
 
+#if HAVE_DECL_GETC_UNLOCKED
+# define pkgconf_getc(stream) getc_unlocked(stream)
+#else
+# define pkgconf_getc(stream) getc(stream)
+#endif
+
 bool
 pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream)
 {
 	bool quoted = false;
-	int c = '\0', c2;
+	bool got_data = false;
+	char run[256];
+	size_t runlen = 0;
+	int c;
 
-	while ((c = getc(stream)) != EOF)
+	while ((c = pkgconf_getc(stream)) != EOF)
 	{
+		got_data = true;
+
 		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 (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;
-			}
+		if (c == '\r')
+		{
+			int next = pkgconf_getc(stream);
 
-			ungetc(c2, stream);
+			if (next != '\n' && next != EOF && ungetc(next, stream) == EOF)
+				return false;
 
 			if (quoted)
 			{
@@ -91,23 +70,26 @@ pkgconf_fgetline(pkgconf_buffer_t *buffe
 
 			break;
 		}
-		else
+
+		if (runlen > sizeof run - 2)
 		{
-			if (quoted) {
-				pkgconf_buffer_push_byte(buffer, '\\');
-				quoted = false;
-			}
-			pkgconf_buffer_push_byte(buffer, c);
+			if (!pkgconf_buffer_append_slice(buffer, run, runlen))
+				return false;
+
+			runlen = 0;
 		}
 
-	}
+		if (quoted)
+		{
+			run[runlen++] = '\\';
+			quoted = false;
+		}
 
-	/* Remove newline character. */
-	if (pkgconf_buffer_lastc(buffer) == '\n')
-		pkgconf_buffer_trim_byte(buffer);
+		run[runlen++] = (char) c;
+	}
 
-	if (pkgconf_buffer_lastc(buffer) == '\r')
-		pkgconf_buffer_trim_byte(buffer);
+	if (runlen != 0 && !pkgconf_buffer_append_slice(buffer, run, runlen))
+		return false;
 
-	return !(c == EOF || ferror(stream));
+	return got_data && !ferror(stream);
 }
Index: libpkgconf/fragment.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/fragment.c,v
diff -u -p -r1.1.1.1 fragment.c
--- libpkgconf/fragment.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/fragment.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * fragment.c
  * Management of fragment lists.
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012, 2013, 2014 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -13,9 +15,19 @@
  * from the use of this software.
  */
 
+#include <libpkgconf/config.h>
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
 
+#ifndef _WIN32
+#include <locale.h>
+#include <langinfo.h>
+#endif
+
+#ifdef __APPLE__
+#include <xlocale.h>
+#endif
+
 /*
  * !doc
  *
@@ -28,11 +40,107 @@
  */
 
 struct pkgconf_fragment_check {
-	char *token;
+	const char *token;
 	size_t len;
 };
 
 static inline bool
+pkgconf_fragment_is_greedy(const char *string)
+{
+	static const struct pkgconf_fragment_check check_fragments[] = {
+		{"-F", 2},
+		{"-I", 2},
+		{"-L", 2},
+		{"-D", 2},
+		{"-l", 2},
+	};
+
+	if (*string != '-')
+		return false;
+
+	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
+		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
+		{
+			/* if it is the bare flag, then we want the next token to be the data */
+			if (!*(string + check_fragments[i].len))
+				return true;
+		}
+
+	return false;
+}
+
+/*
+ * Length of the flag introducing a path which sysroot may be injected into, or
+ * 0 if this is not such a flag.  The length matters as well as the answer: the
+ * path starts there, and it is the path which has to be judged, not the whole
+ * fragment.
+ */
+static inline size_t
+pkgconf_fragment_sysroot_path_offset(const char *string)
+{
+	static const struct pkgconf_fragment_check check_fragments[] = {
+		{"-F", 2},
+		{"-I", 2},
+		{"-L", 2},
+		{"-isystem", 8},
+		{"-idirafter", 10},
+	};
+
+	if (*string != '-')
+		return 0;
+
+	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
+		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
+			return check_fragments[i].len;
+
+	return 0;
+}
+
+/*
+ * Length of a flag which names its path as a separate argument, or 0 if this is
+ * not one of those.
+ */
+static inline size_t
+pkgconf_fragment_separate_path_offset(const char *string)
+{
+	static const struct pkgconf_fragment_check check_fragments[] = {
+		{"-isystem", 8},
+		{"-idirafter", 10},
+	};
+
+	if (*string != '-')
+		return 0;
+
+	for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(check_fragments); i++)
+		if (!strncmp(string, check_fragments[i].token, check_fragments[i].len))
+			return check_fragments[i].len;
+
+	return 0;
+}
+
+/*
+ * Whether `path` already lies under the sysroot, in which case injecting it
+ * again would only produce a doubled prefix.
+ *
+ * This is read off the path rather than remembered from the expansion which
+ * produced it.  A fragment carries everything the question needs, so the answer
+ * holds per fragment however the .pc file arrived at the path -- whether the
+ * sysroot came from ${pc_sysrootdir}, from a variable which happens to sit under
+ * it, or was written out longhand -- and survives the path being split out of a
+ * larger expansion, where provenance would not.
+ */
+static inline bool
+pkgconf_fragment_under_sysroot(const pkgconf_client_t *client, const char *path)
+{
+	size_t len = strlen(client->sysroot_dir);
+
+	if (strncmp(path, client->sysroot_dir, len))
+		return false;
+
+	return path[len] == '/' || path[len] == '\0';
+}
+
+static inline bool
 pkgconf_fragment_is_unmergeable(const char *string)
 {
 	static const struct pkgconf_fragment_check check_fragments[] = {
@@ -70,13 +178,18 @@ pkgconf_fragment_is_unmergeable(const ch
 }
 
 static inline bool
-pkgconf_fragment_should_munge(const char *string, const char *sysroot_dir)
+pkgconf_fragment_only_group_one(const char *string)
 {
-	if (*string != '/')
-		return false;
+	static const struct pkgconf_fragment_check check_fragments[] = {
+		{"-framework", 10},
+		{"-isystem", 8},
+		{"-idirafter", 10},
+		{"-include", 8},
+	};
 
-	if (sysroot_dir != NULL && strncmp(sysroot_dir, string, strlen(sysroot_dir)))
-		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;
 
 	return false;
 }
@@ -86,12 +199,11 @@ pkgconf_fragment_is_groupable(const char
 {
 	static const struct pkgconf_fragment_check check_fragments[] = {
 		{"-Wl,--start-group", 17},
-		{"-framework", 10},
-		{"-isystem", 8},
-		{"-idirafter", 10},
-		{"-include", 8},
 	};
 
+	if (pkgconf_fragment_only_group_one(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;
@@ -100,12 +212,15 @@ pkgconf_fragment_is_groupable(const char
 }
 
 static inline bool
-pkgconf_fragment_is_terminus(const char *string)
+pkgconf_fragment_is_terminus(const char *parent, const char *string)
 {
 	static const struct pkgconf_fragment_check check_fragments[] = {
 		{"-Wl,--end-group", 15},
 	};
 
+	if (pkgconf_fragment_only_group_one(parent))
+		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;
@@ -125,32 +240,24 @@ pkgconf_fragment_is_special(const char *
 	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)
+static pkgconf_fragment_t *
+fragment_new(char type, const char *data)
 {
-	*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");
+	size_t datalen = data != NULL ? strlen(data) : 0;
+	pkgconf_fragment_t *frag = calloc(1, sizeof(*frag) + (data != NULL ? datalen + 1 : 0));
 
-		if (sysroot_dir != NULL && pkgconf_fragment_should_munge(source, sysroot_dir))
-			pkgconf_strlcat(buf, sysroot_dir, buflen);
-	}
+	if (frag == NULL)
+		return NULL;
 
-	pkgconf_strlcat(buf, source, buflen);
+	frag->type = type;
 
-	if (*buf == '/' && !(client->flags & PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS))
-		pkgconf_path_relocate(buf, buflen);
-}
+	if (data != NULL)
+	{
+		frag->data = (char *)(frag + 1);
+		memcpy(frag->data, data, datalen + 1);
+	}
 
-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);
+	return frag;
 }
 
 /*
@@ -168,13 +275,15 @@ pkgconf_fragment_copy_munged(const pkgco
  *    :return: nothing
  */
 void
-pkgconf_fragment_insert(const pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail)
+pkgconf_fragment_insert(pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail)
 {
+	(void) client;
+
 	pkgconf_fragment_t *frag;
 
-	frag = calloc(1, sizeof(pkgconf_fragment_t));
-	frag->type = type;
-	frag->data = pkgconf_fragment_copy_munged(client, data, 0);
+	frag = fragment_new(type, data);
+	if (frag == NULL)
+		return;
 
 	if (tail)
 	{
@@ -185,68 +294,301 @@ pkgconf_fragment_insert(const pkgconf_cl
 	pkgconf_node_insert(&frag->iter, frag, list);
 }
 
+static bool
+should_inject_sysroot(const pkgconf_client_t *client, const char *string, unsigned int flags)
+{
+	size_t offset;
+
+	if (client->flags & PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION)
+		return false;
+
+	/* we never automatically inject sysroot on -uninstalled packages */
+	if (flags & PKGCONF_PKG_PROPF_UNINSTALLED)
+	{
+		/* ... unless we are emulating pkgconf 1.x */
+		if (!(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))
+			return false;
+	}
+
+	if (client->sysroot_dir == NULL)
+		return false;
+
+	offset = pkgconf_fragment_sysroot_path_offset(string);
+	if (offset == 0)
+		return false;
+
+	return !pkgconf_fragment_under_sysroot(client, string + offset);
+}
+
+static bool
+should_inject_sysroot_child(const pkgconf_client_t *client, const pkgconf_fragment_t *last, const char *string, unsigned int flags)
+{
+	if (client->flags & PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION)
+		return false;
+
+	/* we never automatically inject sysroot on -uninstalled packages */
+	if (flags & PKGCONF_PKG_PROPF_UNINSTALLED)
+	{
+		/* ... unless we are emulating pkgconf 1.x */
+		if (!(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))
+			return false;
+	}
+
+	if (last->type)
+		return false;
+
+	if (last->data == NULL)
+		return false;
+
+	if (client->sysroot_dir == NULL)
+		return false;
+
+	/* the flag is the preceding fragment; this one is the bare path it takes.
+	 * a flag which has already taken its argument takes no further ones, so
+	 * what follows is a fragment in its own right and not a path at all. */
+	if (last->flags & PKGCONF_PKG_FRAGF_TERMINATED)
+		return false;
+
+	if (pkgconf_fragment_sysroot_path_offset(last->data) == 0)
+		return false;
+
+	return !pkgconf_fragment_under_sysroot(client, string);
+}
+
 /*
- * !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
+ * Insert an already-expanded fragment string into the list.  No variable
+ * substitution is performed here: `string` is taken verbatim, so this must
+ * only be called with input that has already been through the bytecode
+ * evaluator.  Whether sysroot injection is still wanted is read off `string`
+ * itself; see pkgconf_fragment_under_sysroot.
  */
-void
-pkgconf_fragment_add(const pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags)
+static bool
+fragment_insert_evaluated(pkgconf_client_t *client, pkgconf_list_t *list, const char *string, unsigned int flags)
 {
 	pkgconf_list_t *target = list;
+	pkgconf_fragment_t *terminate_parent = NULL;
 	pkgconf_fragment_t *frag;
+	size_t separate;
 
-	if (*string == '\0')
-		return;
+	if (string == NULL || *string == '\0')
+		return true;
+
+	/* A .pc file may join one of these flags to its path.  That names the same
+	 * thing as writing them apart, so split it back apart and let there be one
+	 * spelling of it in a fragment list: the flag, and the path it takes. */
+	separate = pkgconf_fragment_separate_path_offset(string);
+	if (separate != 0 && string[separate] != '\0')
+	{
+		pkgconf_buffer_t flagbuf = PKGCONF_BUFFER_INITIALIZER;
+		bool ok = pkgconf_buffer_append_slice(&flagbuf, string, separate) &&
+			fragment_insert_evaluated(client, list, pkgconf_buffer_str(&flagbuf), flags) &&
+			fragment_insert_evaluated(client, list, string + separate, flags);
+
+		pkgconf_buffer_finalize(&flagbuf);
+		return ok;
+	}
 
 	if (list->tail != NULL && list->tail->data != NULL &&
-	    !(client->flags & PKGCONF_PKG_PKGF_DONT_MERGE_SPECIAL_FRAGMENTS))
+		!(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))
+			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;
+			if (pkgconf_fragment_is_terminus(parent->data, string))
+				terminate_parent = parent;
 
 			PKGCONF_TRACE(client, "adding fragment as child to list @%p", target);
 		}
 	}
 
-	if (strlen(string) > 1 && !pkgconf_fragment_is_special(string))
-	{
-		frag = calloc(1, sizeof(pkgconf_fragment_t));
+	/* Compute the final data string first (borrowing sysroot_buf when we have to
+	 * prepend the sysroot), then hand it to fragment_new(), which copies it
+	 * inline.  data == NULL here means an allocation/append failure. */
+	{
+		char type = 0;
+		const char *data = NULL;
+		pkgconf_buffer_t sysroot_buf = PKGCONF_BUFFER_INITIALIZER;
+
+		if (strlen(string) > 1 && !pkgconf_fragment_is_special(string))
+		{
+			type = *(string + 1);
 
-		frag->type = *(string + 1);
-		frag->data = pkgconf_fragment_copy_munged(client, string + 2, flags);
+			if (should_inject_sysroot(client, string, flags))
+			{
+				if (pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir) &&
+					pkgconf_buffer_append(&sysroot_buf, string + 2))
+					data = pkgconf_buffer_str(&sysroot_buf);
+			}
+			else
+				data = string + 2;
+		}
+		else
+		{
+			type = 0;
 
-		PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list);
+			if (client->sysroot_dir != NULL && list->tail != NULL && list->tail->data != NULL &&
+				should_inject_sysroot_child(client, list->tail->data, string, flags))
+			{
+				if (pkgconf_buffer_append(&sysroot_buf, client->sysroot_dir) &&
+					pkgconf_buffer_append(&sysroot_buf, string))
+					data = pkgconf_buffer_str(&sysroot_buf);
+			}
+			else
+				data = string;
+		}
+
+		frag = data != NULL ? fragment_new(type, data) : NULL;
+		pkgconf_buffer_finalize(&sysroot_buf);
 	}
+
+	if (frag == NULL)
+	{
+		PKGCONF_TRACE(client, "failed to add new fragment due to allocation failure to list @%p", target);
+		return false;
+	}
+
+	if (frag->type)
+		PKGCONF_TRACE(client, "added fragment {%c, '%s'} to list @%p", frag->type, frag->data, list);
 	else
+		PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target);
+
+	pkgconf_node_insert_tail(&frag->iter, frag, target);
+	if (terminate_parent != NULL)
+		terminate_parent->flags |= PKGCONF_PKG_FRAGF_TERMINATED;
+
+	return true;
+}
+
+/*
+ * Split a string into whitespace-delimited fragments, honouring shell quoting
+ * and greedy flags (e.g. "-I /usr/include" -> "-I/usr/include").
+ *
+ * When `evaluate` is true, `value` is a property as the .pc file spells it and
+ * each token is handed to pkgconf_fragment_add to be expanded.  Splitting it
+ * only settles where the tokens end, so the quoting is left in place for the
+ * pass over the expansion to consume.
+ *
+ * When `evaluate` is false, `value` is the expansion of one such token.  This is
+ * where the quoting is consumed, once, over text which is otherwise finished,
+ * and the tokens are inserted as they stand.  It must not evaluate again, lest
+ * an expansion yielding a literal "${...}" (via a "$$" escape) recurse forever.
+ */
+static bool
+fragment_split(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags, bool evaluate)
+{
+	int i, ret, argc;
+	char **argv;
+
+	ret = evaluate
+		? pkgconf_argv_split_raw(value, &argc, &argv)
+		: pkgconf_argv_split(value, &argc, &argv);
+	if (ret < 0)
 	{
-		frag = calloc(1, sizeof(pkgconf_fragment_t));
+		PKGCONF_TRACE(client, "unable to parse fragment string [%s]", value);
+		return false;
+	}
 
-		frag->type = 0;
-		frag->data = pkgconf_fragment_copy_munged(client, string, flags);
+	for (i = 0; i < argc; i++)
+	{
+		const char *token;
+		pkgconf_buffer_t greedybuf = PKGCONF_BUFFER_INITIALIZER;
+		bool ok;
 
-		PKGCONF_TRACE(client, "created special fragment {'%s'} in list @%p", frag->data, target);
+		if (argv[i] == NULL)
+		{
+			PKGCONF_TRACE(client, "parsed fragment string is inconsistent: argc = %d while argv[%d] == NULL", argc, i);
+			pkgconf_argv_free(argv);
+			return false;
+		}
+
+		token = argv[i];
+
+		PKGCONF_TRACE(client, "processing [%s]", argv[i]);
+
+		if (pkgconf_fragment_is_greedy(argv[i]) && i + 1 < argc)
+		{
+			if (!pkgconf_buffer_append(&greedybuf, argv[i]) ||
+				!pkgconf_buffer_append(&greedybuf, argv[i + 1]))
+			{
+				pkgconf_buffer_finalize(&greedybuf);
+				pkgconf_argv_free(argv);
+				return false;
+			}
+
+			token = pkgconf_buffer_str(&greedybuf);
+
+			/* skip over next arg as we combined them */
+			i++;
+		}
+
+		if (evaluate)
+			ok = pkgconf_fragment_add(client, list, vars, token, flags);
+		else
+			ok = fragment_insert_evaluated(client, list, token, flags);
+
+		pkgconf_buffer_finalize(&greedybuf);
+
+		if (!ok)
+		{
+			pkgconf_argv_free(argv);
+			return false;
+		}
 	}
 
-	pkgconf_node_insert_tail(&frag->iter, frag, target);
+	pkgconf_argv_free(argv);
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool 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: true on success, false on parse error or allocation failure
+ */
+bool
+pkgconf_fragment_add(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags)
+{
+	pkgconf_buffer_t evalbuf = PKGCONF_BUFFER_INITIALIZER;
+	bool ret;
+
+	if (!pkgconf_bytecode_eval_str_to_buf(client, vars, value, NULL, &evalbuf))
+	{
+		pkgconf_buffer_finalize(&evalbuf);
+		return false;
+	}
+
+	if (pkgconf_buffer_len(&evalbuf) == 0)
+	{
+		pkgconf_buffer_finalize(&evalbuf);
+		return true;
+	}
+
+	/* fragment_split() only reads the expanded string, so hand it the buffer
+	 * contents directly rather than freezing (copying) them out.
+	 *
+	 * The expansion is split rather than taken whole: the quoting it carries is
+	 * consumed there, and a value may in any case expand to several
+	 * whitespace-separated fragments.
+	 */
+	ret = fragment_split(client, list, vars, pkgconf_buffer_str(&evalbuf), flags, false);
+
+	pkgconf_buffer_finalize(&evalbuf);
+	return ret;
 }
 
 static inline pkgconf_fragment_t *
@@ -258,14 +600,115 @@ pkgconf_fragment_lookup(pkgconf_list_t *
 	{
 		pkgconf_fragment_t *frag = node->data;
 
-		if (base->type != frag->type)
-			continue;
+		if (base->type != frag->type)
+			continue;
+
+		if (base->data == NULL || frag->data == NULL)
+		{
+			if (base->data == frag->data)
+				return frag;
+
+			continue;
+		}
+
+		if (!strcmp(base->data, frag->data))
+			return frag;
+	}
+
+	return NULL;
+}
+
+/* Order fragments by (type, data) so that the cursor index can be searched
+ * with bsearch().  NULL data sorts before any non-NULL data of the same type. */
+static int
+fragment_index_cmp(const void *a, const void *b)
+{
+	const pkgconf_fragment_t *fa = *(pkgconf_fragment_t * const *) a;
+	const pkgconf_fragment_t *fb = *(pkgconf_fragment_t * const *) b;
+
+	if (fa->type != fb->type)
+		return (unsigned char) fa->type < (unsigned char) fb->type ? -1 : 1;
+
+	if (fa->data == NULL || fb->data == NULL)
+	{
+		if (fa->data == fb->data)
+			return 0;
+
+		return fa->data == NULL ? -1 : 1;
+	}
+
+	return strcmp(fa->data, fb->data);
+}
+
+static pkgconf_fragment_t *
+fragment_index_lookup(const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base)
+{
+	pkgconf_fragment_t **found;
+
+	if (cursor->count == 0)
+		return NULL;
+
+	found = bsearch(&base, cursor->index, cursor->count, sizeof(void *), fragment_index_cmp);
+	return found != NULL ? *found : NULL;
+}
+
+static bool
+fragment_index_insert(pkgconf_fragment_cursor_t *cursor, pkgconf_fragment_t *frag)
+{
+	size_t lo = 0, hi = cursor->count;
+
+	if (cursor->count == cursor->alloc)
+	{
+		size_t newalloc = cursor->alloc != 0 ? cursor->alloc * 2 : 16;
+		pkgconf_fragment_t **newindex = pkgconf_reallocarray(cursor->index, newalloc, sizeof(void *));
+
+		if (newindex == NULL)
+			return false;
+
+		cursor->index = newindex;
+		cursor->alloc = newalloc;
+	}
+
+	while (lo < hi)
+	{
+		size_t mid = lo + (hi - lo) / 2;
+
+		if (fragment_index_cmp(&cursor->index[mid], &frag) < 0)
+			lo = mid + 1;
+		else
+			hi = mid;
+	}
+
+	memmove(&cursor->index[lo + 1], &cursor->index[lo], (cursor->count - lo) * sizeof(void *));
+	cursor->index[lo] = frag;
+	cursor->count++;
+
+	return true;
+}
+
+static void
+fragment_index_remove(pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *frag)
+{
+	pkgconf_fragment_t **found = bsearch(&frag, cursor->index, cursor->count, sizeof(void *), fragment_index_cmp);
+	size_t idx;
+
+	if (found == NULL)
+		return;
 
-		if (!strcmp(base->data, frag->data))
-			return frag;
-	}
+	idx = (size_t) (found - cursor->index);
+	memmove(&cursor->index[idx], &cursor->index[idx + 1], (cursor->count - idx - 1) * sizeof(void *));
+	cursor->count--;
+}
 
-	return NULL;
+/* Look up an existing fragment matching `base`: via the cursor's sorted index
+ * when one is provided, otherwise a linear scan of the list. */
+static inline pkgconf_fragment_t *
+fragment_lookup(pkgconf_list_t *list, const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base)
+{
+	if (cursor != NULL)
+		return fragment_index_lookup(cursor, base);
+
+	return pkgconf_fragment_lookup(list, base);
 }
 
 static inline bool
@@ -302,11 +745,14 @@ pkgconf_fragment_can_merge(const pkgconf
 	if (base->children.head != NULL)
 		return false;
 
+	if (base->data == 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)
+pkgconf_fragment_exists(pkgconf_list_t *list, const pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, unsigned int flags, bool is_private)
 {
 	if (!pkgconf_fragment_can_merge_back(base, flags, is_private))
 		return NULL;
@@ -314,7 +760,7 @@ pkgconf_fragment_exists(pkgconf_list_t *
 	if (!pkgconf_fragment_can_merge(base, flags, is_private))
 		return NULL;
 
-	return pkgconf_fragment_lookup(list, base);
+	return fragment_lookup(list, cursor, base);
 }
 
 static inline bool
@@ -375,6 +821,68 @@ pkgconf_fragment_has_system_dir(const pk
 	return pkgconf_path_match_list(frag->data, check_paths);
 }
 
+static bool
+pkgconf_fragment_copy_node(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private);
+
+static bool
+pkgconf_fragment_copy_list_node(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;
+
+		/* children are copied into their own (small) list, which is never
+		 * indexed, so no cursor is threaded down here. */
+		if (!pkgconf_fragment_copy_node(client, list, NULL, frag, true))
+			return false;
+	}
+
+	return true;
+}
+
+static bool
+pkgconf_fragment_copy_node(const pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private)
+{
+	pkgconf_fragment_t *old_frag = NULL;
+	pkgconf_fragment_t *frag;
+
+	if ((old_frag = pkgconf_fragment_exists(list, cursor, base, client->flags, is_private)) != NULL)
+	{
+		if (!pkgconf_fragment_should_merge(old_frag))
+			old_frag = NULL;
+	}
+	else if (!is_private && !pkgconf_fragment_can_merge_back(base, client->flags, is_private) && (fragment_lookup(list, cursor, base) != NULL))
+		return true;
+
+	frag = fragment_new(base->type, base->data);
+	if (frag == NULL)
+		return false;
+
+	if (!pkgconf_fragment_copy_list_node(client, &frag->children, &base->children))
+	{
+		pkgconf_fragment_free(&frag->children);
+		free(frag);
+		return false;
+	}
+
+	if (old_frag != NULL)
+	{
+		if (cursor != NULL)
+			fragment_index_remove(cursor, old_frag);
+
+		pkgconf_fragment_delete(list, old_frag);
+	}
+
+	pkgconf_node_insert_tail(&frag->iter, frag, list);
+
+	if (cursor != NULL && !fragment_index_insert(cursor, frag))
+		return false;
+
+	return true;
+}
+
 /*
  * !doc
  *
@@ -392,24 +900,76 @@ pkgconf_fragment_has_system_dir(const pk
 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;
+	(void) pkgconf_fragment_copy_node(client, list, NULL, base, is_private);
+}
 
-	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;
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list)
+ *
+ *    Initialises a `fragment cursor` bound to a (typically empty) destination list.  While the
+ *    cursor is in use, fragments must be added to the list only via ``pkgconf_fragment_copy_cursor()``
+ *    so that the cursor's index stays in sync.
+ *
+ *    :param pkgconf_fragment_cursor_t* cursor: The cursor to initialise.
+ *    :param pkgconf_list_t* list: The destination fragment list.
+ *    :return: nothing
+ */
+void
+pkgconf_fragment_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list)
+{
+	pkgconf_node_t *node;
 
-	frag = calloc(1, sizeof(pkgconf_fragment_t));
+	cursor->list = list;
+	cursor->index = NULL;
+	cursor->count = 0;
+	cursor->alloc = 0;
 
-	frag->type = base->type;
-	pkgconf_fragment_copy_list(client, &frag->children, &base->children);
-	if (base->data != NULL)
-		frag->data = strdup(base->data);
+	/* seed the index with anything already present, so dedup against pre-existing
+	 * fragments behaves exactly as the linear scan would. */
+	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+		(void) fragment_index_insert(cursor, node->data);
+}
 
-	pkgconf_node_insert_tail(&frag->iter, frag, list);
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor)
+ *
+ *    Releases the index held by a `fragment cursor`.  The destination list and its fragments are
+ *    not affected.
+ *
+ *    :param pkgconf_fragment_cursor_t* cursor: The cursor to release.
+ *    :return: nothing
+ */
+void
+pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor)
+{
+	free(cursor->index);
+	cursor->index = NULL;
+	cursor->count = 0;
+	cursor->alloc = 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_copy_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private)
+ *
+ *    Like ``pkgconf_fragment_copy()``, but uses the cursor's sorted index for the mergeback lookup,
+ *    turning what would be a linear scan of the destination list into a bsearch.
+ *
+ *    :param pkgconf_client_t* client: The pkgconf client being accessed.
+ *    :param pkgconf_fragment_cursor_t* cursor: The cursor bound to the destination list.
+ *    :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_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, const pkgconf_fragment_t *base, bool is_private)
+{
+	(void) pkgconf_fragment_copy_node(client, cursor->list, cursor, base, is_private);
 }
 
 /*
@@ -428,14 +988,7 @@ pkgconf_fragment_copy(const pkgconf_clie
 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);
-	}
+	(void) pkgconf_fragment_copy_list_node(client, list, base);
 }
 
 /*
@@ -462,231 +1015,227 @@ pkgconf_fragment_filter(const pkgconf_cl
 		pkgconf_fragment_t *frag = node->data;
 
 		if (filter_func(client, frag, data))
-			pkgconf_fragment_copy(client, dest, frag, true);
+			(void) pkgconf_fragment_copy_node(client, dest, NULL, frag, true);
 	}
 }
 
-static inline char *
-fragment_quote(const pkgconf_fragment_t *frag)
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_fragment_filter_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data)
+ *
+ *    Like ``pkgconf_fragment_filter()``, but moves the matching `fragments` to `dest` instead of
+ *    copying them.  Fragments which do not match are left in `src` for the caller to release.
+ *
+ *    :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_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data)
 {
-	const char *src = frag->data;
-	ssize_t outlen = strlen(src) + 10;
-	char *out, *dst;
-
-	if (frag->data == NULL)
-		return NULL;
+	pkgconf_node_t *node, *next;
 
-	out = dst = calloc(1, outlen);
+	if (dest == src)
+		return;
 
-	for (; *src; src++)
+	PKGCONF_FOREACH_LIST_ENTRY_SAFE(src->head, next, node)
 	{
-		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;
-			out = realloc(out, outlen);
-			dst = out + offset;
-		}
-	}
-
-	*dst = 0;
-	return out;
-}
-
-static inline size_t
-pkgconf_fragment_len(const pkgconf_fragment_t *frag)
-{
-	size_t len = 1;
+		pkgconf_fragment_t *frag = node->data;
 
-	if (frag->type)
-		len += 2;
+		if (!filter_func(client, frag, data))
+			continue;
 
-	if (frag->data != NULL)
-	{
-		pkgconf_node_t *iter;
+		pkgconf_node_delete(node, src);
 
-		char *quoted = fragment_quote(frag);
-		len += strlen(quoted);
-		free(quoted);
+		/* pkgconf_node_delete() leaves the node's links pointing into the
+		 * source list, and pkgconf_node_insert_tail() only overwrites prev. */
+		node->prev = node->next = NULL;
 
-		PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
-		{
-			const pkgconf_fragment_t *child_frag = iter->data;
-			len += pkgconf_fragment_len(child_frag) + 1;
-		}
+		pkgconf_node_insert_tail(node, frag, dest);
 	}
+}
 
-	return len;
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_is_locale_utf8(void)
+ *
+ *    Check whether text is expected to be UTF-8 encoded in the current environment.
+ *
+ *    :return: :code:`true` if text is expected to be UTF-8 encoded, :code:`false` otherwise.
+ */
+#ifndef _WIN32
+static bool
+codeset_is_utf8(const char *codeset)
+{
+	return codeset != NULL && (!strcasecmp(codeset, "UTF-8") || !strcasecmp(codeset, "UTF8"));
 }
+#endif
 
-static size_t
-fragment_render_len(const pkgconf_list_t *list, bool escape)
+bool
+pkgconf_is_locale_utf8(void)
 {
-	(void) escape;
+#ifdef _WIN32
+	return GetACP() == CP_UTF8;
+#else
+	static int cached = -1;
 
-	size_t out = 1;		/* trailing nul */
-	pkgconf_node_t *node;
+	if (cached >= 0)
+		return cached;
 
-	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+#if HAVE_DECL_NL_LANGINFO_L
+	locale_t loc = newlocale(LC_CTYPE_MASK, "", (locale_t)0);
+
+	if (loc != (locale_t)0)
 	{
-		const pkgconf_fragment_t *frag = node->data;
-		out += pkgconf_fragment_len(frag);
+		cached = codeset_is_utf8(nl_langinfo_l(CODESET, loc));
+		freelocale(loc);
 	}
+	else
+		cached = 0;
+#else
+	const char *prev_locale = setlocale(LC_CTYPE, NULL);
+	char *saved_locale = prev_locale != NULL ? strdup(prev_locale) : NULL;
+
+	setlocale(LC_CTYPE, "");
+	cached = codeset_is_utf8(nl_langinfo(CODESET));
+	setlocale(LC_CTYPE, saved_locale != NULL ? saved_locale : "C");
+	free(saved_locale);
+#endif
 
-	return out;
+	return cached;
+#endif
 }
 
-static inline size_t
-fragment_render_item(const pkgconf_fragment_t *frag, char *bptr, size_t bufremain)
-{
-	char *base = bptr;
-	char *quoted = fragment_quote(frag);
-	const pkgconf_node_t *iter;
+static const pkgconf_span_t quote_spans[] = {
+	{ 0x00, 0x1f },
+	{ (unsigned char)' ', (unsigned char)'#' },
+	{ (unsigned char)'%', (unsigned char)'\'' },
+	{ (unsigned char)'*', (unsigned char)'*' },
+	{ (unsigned char)';', (unsigned char)'<' },
+	{ (unsigned char)'>', (unsigned char)'?' },
+	{ (unsigned char)'[', (unsigned char)']' },
+	{ (unsigned char)'`', (unsigned char)'`' },
+	{ (unsigned char)'{', (unsigned char)'}' },
+	{ 0x7f, 0xff },
+};
 
-	if (strlen(quoted) > bufremain)
-	{
-		free(quoted);
-		return 0;
-	}
+/* If the locale is UTF-8 we must not split character over 0x7f because it would add "\" between each bytes.
+   So only DEL (0x7f) needs escaping */
+static const pkgconf_span_t quote_spans_utf8[] = {
+	{ 0x00, 0x1f },
+	{ (unsigned char)' ', (unsigned char)'#' },
+	{ (unsigned char)'%', (unsigned char)'\'' },
+	{ (unsigned char)'*', (unsigned char)'*' },
+	{ (unsigned char)';', (unsigned char)'<' },
+	{ (unsigned char)'>', (unsigned char)'?' },
+	{ (unsigned char)'[', (unsigned char)']' },
+	{ (unsigned char)'`', (unsigned char)'`' },
+	{ (unsigned char)'{', (unsigned char)'}' },
+	{ 0x7f, 0x7f },
+};
 
-	if (frag->type)
-	{
-		*bptr++ = '-';
-		*bptr++ = frag->type;
-	}
+/*
+ * The set of bytes needing a backslash is fixed for the life of the process
+ * (pkgconf_is_locale_utf8() is itself cached), so resolve the spans into a
+ * byte-set once instead of walking them for every byte of every fragment.
+ */
+static const pkgconf_charset_t *
+fragment_quote_charset(void)
+{
+	static pkgconf_charset_t charset;
+	static bool have_charset = false;
 
-	if (quoted != NULL)
+	if (!have_charset)
 	{
-		bptr += pkgconf_strlcpy(bptr, quoted, bufremain - (bptr - base));
-		free(quoted);
+		if (pkgconf_is_locale_utf8())
+			pkgconf_charset_from_spans(&charset, quote_spans_utf8, PKGCONF_ARRAY_SIZE(quote_spans_utf8));
+		else
+			pkgconf_charset_from_spans(&charset, quote_spans, PKGCONF_ARRAY_SIZE(quote_spans));
+
+		have_charset = true;
 	}
 
-	PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
-	{
-		const pkgconf_fragment_t *child_frag = iter->data;
+	return &charset;
+}
 
-		*bptr++ = ' ';
-		bptr += fragment_render_item(child_frag, bptr, bufremain - (bptr - base));
-	}
+static bool
+fragment_quote(pkgconf_buffer_t *out, const pkgconf_fragment_t *frag)
+{
+	if (frag->data == NULL)
+		return true;
 
-	return bptr - base;
+	return pkgconf_buffer_escape_charset(out, PKGCONF_BUFFER_FROM_STR(frag->data), fragment_quote_charset());
 }
 
-static void
-fragment_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape)
+static bool
+fragment_render(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf)
 {
-	(void) escape;
+	const pkgconf_node_t *iter;
 
-	pkgconf_node_t *node;
-	char *bptr = buf;
+	if (frag->type &&
+		(!pkgconf_buffer_push_byte(buf, '-') || !pkgconf_buffer_push_byte(buf, frag->type)))
+		return false;
 
-	memset(buf, 0, buflen);
+	if (!fragment_quote(buf, frag))
+		return false;
 
-	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+	PKGCONF_FOREACH_LIST_ENTRY(frag->children.head, iter)
 	{
-		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;
+		const pkgconf_fragment_t *child_frag = iter->data;
 
-		if (node->next != NULL)
-			*bptr++ = ' ';
+		if (!pkgconf_buffer_push_byte(buf, ctx->delim) ||
+			!fragment_render(ctx, child_frag, buf))
+			return false;
 	}
+
+	return true;
 }
 
 static const pkgconf_fragment_render_ops_t default_render_ops = {
-	.render_len = fragment_render_len,
-	.render_buf = fragment_render_buf
+	.render = fragment_render
 };
 
 /*
  * !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)
+ * .. 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, char delim)
  *
  *    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 pkgconf_buffer_t* buf: The buffer to render the fragment list into.
  *    :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``.
+ *    :param char delim: The delimiter to use between fragments.
  *    :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)
+bool
+pkgconf_fragment_render_buf(const pkgconf_list_t *list, pkgconf_buffer_t *buf, bool escape, const pkgconf_fragment_render_ops_t *ops, char delim)
 {
-	(void) escape;
+	pkgconf_node_t *node;
+	pkgconf_fragment_render_ctx_t ctx = {
+		.escape = escape,
+		.delim = delim,
+	};
 
 	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_FOREACH_LIST_ENTRY(list->head, node)
+	{
+		const pkgconf_fragment_t *frag = node->data;
+		if (!ops->render(&ctx, frag, buf))
+			return false;
 
-	pkgconf_fragment_render_buf(list, buf, buflen, true, ops);
+		if (node->next != NULL && !pkgconf_buffer_push_byte(buf, ctx.delim))
+			return false;
+	}
 
-	return buf;
+	return true;
 }
 
 /*
@@ -705,7 +1254,7 @@ pkgconf_fragment_delete(pkgconf_list_t *
 {
 	pkgconf_node_delete(&node->iter, list);
 
-	free(node->data);
+	pkgconf_fragment_free(&node->children);
 	free(node);
 }
 
@@ -729,9 +1278,10 @@ pkgconf_fragment_free(pkgconf_list_t *li
 		pkgconf_fragment_t *frag = node->data;
 
 		pkgconf_fragment_free(&frag->children);
-		free(frag->data);
 		free(frag);
 	}
+
+	pkgconf_list_zero(list);
 }
 
 /*
@@ -749,39 +1299,7 @@ pkgconf_fragment_free(pkgconf_list_t *li
  *    :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)
+pkgconf_fragment_parse(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;
+	return fragment_split(client, list, vars, value, flags, true);
 }
Index: libpkgconf/iter.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/iter.h,v
diff -u -p -r1.1.1.1 iter.h
--- libpkgconf/iter.h	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/iter.h	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * iter.h
  * Linked lists and iterators.
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2013 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -16,6 +18,8 @@
 #ifndef LIBPKGCONF_ITER_H
 #define LIBPKGCONF_ITER_H
 
+#include <stddef.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -40,6 +44,26 @@ pkgconf_list_zero(pkgconf_list_t *list)
 	list->head = NULL;
 	list->tail = NULL;
 	list->length = 0;
+}
+
+static inline void
+pkgconf_list_splice(pkgconf_list_t *dest, pkgconf_list_t *src)
+{
+	if (dest == src || src->head == NULL)
+		return;
+
+	if (dest->head == NULL)
+		dest->head = src->head;
+	else
+	{
+		dest->tail->next = src->head;
+		src->head->prev = dest->tail;
+	}
+
+	dest->tail = src->tail;
+	dest->length += src->length;
+
+	pkgconf_list_zero(src);
 }
 
 static inline void
Index: libpkgconf/libpkgconf.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/libpkgconf.h,v
diff -u -p -r1.1.1.1 libpkgconf.h
--- libpkgconf/libpkgconf.h	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/libpkgconf.h	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * libpkgconf.h
  * Global include file for everything in libpkgconf.
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2011, 2015 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -22,6 +24,7 @@
 #include <stddef.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <string.h>
 #include <libpkgconf/libpkgconf-api.h>
 #include <libpkgconf/iter.h>
 #include <libpkgconf/bsdstubs.h>
@@ -30,23 +33,6 @@
 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 {
@@ -63,12 +49,16 @@ typedef enum {
 
 typedef struct pkgconf_pkg_ pkgconf_pkg_t;
 typedef struct pkgconf_dependency_ pkgconf_dependency_t;
-typedef struct pkgconf_tuple_ pkgconf_tuple_t;
+typedef struct pkgconf_buffer_ pkgconf_buffer_t;
+typedef struct pkgconf_bufferset_ pkgconf_bufferset_t;
+typedef struct pkgconf_span_ pkgconf_span_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;
+typedef struct pkgconf_output_ pkgconf_output_t;
+typedef struct pkgconf_license_ pkgconf_license_t;
 
 #define PKGCONF_ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
 
@@ -81,8 +71,8 @@ typedef struct pkgconf_queue_ pkgconf_qu
 #define PKGCONF_FOREACH_LIST_ENTRY_REVERSE(tail, value) \
 	for ((value) = (tail); (value) != NULL; (value) = (value)->prev)
 
-#define LIBPKGCONF_VERSION	20403
-#define LIBPKGCONF_VERSION_STR	"2.4.3"
+#define LIBPKGCONF_VERSION	30005
+#define LIBPKGCONF_VERSION_STR	"3.0.5"
 
 struct pkgconf_queue_ {
 	pkgconf_node_t iter;
@@ -116,8 +106,62 @@ struct pkgconf_dependency_ {
 
 	int refcount;
 	pkgconf_client_t *owner;
+
+	char *why;
+};
+
+struct pkgconf_buffer_ {
+	char *base;
+	char *end;
+};
+
+struct pkgconf_bufferset_ {
+	pkgconf_node_t node;
+	pkgconf_buffer_t buffer;
+};
+
+#if defined(_MSC_VER) && !defined(__clang__)
+# define PKGCONF_PACKED_STRUCT(name) __pragma(pack(push, 1)) struct name __pragma(pack(pop))
+#else
+# define PKGCONF_PACKED_STRUCT(name) struct __attribute__((__packed__)) name
+#endif
+
+enum pkgconf_bytecode_op {
+	PKGCONF_BYTECODE_OP_TEXT = 1,
+	PKGCONF_BYTECODE_OP_VAR = 2,
+	PKGCONF_BYTECODE_OP_SYSROOT = 3,
 };
 
+typedef PKGCONF_PACKED_STRUCT(pkgconf_bytecode_op_) {
+	enum pkgconf_bytecode_op tag;
+	uint32_t size;
+	char data[];
+} pkgconf_bytecode_op_t;
+
+typedef struct {
+	const uint8_t *base;
+	size_t len;
+} pkgconf_bytecode_t;
+
+typedef struct pkgconf_variable_ {
+	pkgconf_node_t iter;
+
+	char *key;
+
+	pkgconf_buffer_t bcbuf;
+	pkgconf_bytecode_t bc;
+
+	unsigned int flags;
+
+	bool expanding;
+} pkgconf_variable_t;
+
+#define PKGCONF_VARIABLEF_OVERRIDE		0x1
+
+typedef pkgconf_variable_t pkgconf_tuple_t;
+
+#define PKGCONF_PKG_TUPLEF_OVERRIDE		PKGCONF_VARIABLEF_OVERRIDE
+
 struct pkgconf_tuple_ {
 	pkgconf_node_t iter;
 
@@ -127,8 +171,6 @@ struct pkgconf_tuple_ {
 	unsigned int flags;
 };
 
-#define PKGCONF_PKG_TUPLEF_OVERRIDE		0x1
-
 struct pkgconf_path_ {
 	pkgconf_node_t lnode;
 
@@ -139,6 +181,23 @@ struct pkgconf_path_ {
 	unsigned int flags;
 };
 
+typedef enum {
+	PKGCONF_LICENSE_UNKNOWN = 0,
+	PKGCONF_LICENSE_EXPRESSION = 1,
+	PKGCONF_LICENSE_AND = 10,
+	PKGCONF_LICENSE_OR = 11,
+	PKGCONF_LICENSE_WITH = 12,
+	PKGCONF_LICENSE_BRACKET_OPEN = 20,
+	PKGCONF_LICENSE_BRACKET_CLOSE = 21,
+} pkgconf_license_types_t;
+
+struct pkgconf_license_ {
+	pkgconf_node_t iter;
+
+	unsigned char type;
+	char *data;
+};
+
 #define PKGCONF_PKG_PROPF_NONE			0x00
 #define PKGCONF_PKG_PROPF_STATIC		0x01
 #define PKGCONF_PKG_PROPF_CACHED		0x02
@@ -146,6 +205,7 @@ struct pkgconf_path_ {
 #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;
@@ -156,18 +216,27 @@ struct pkgconf_pkg_ {
 	char *description;
 	char *url;
 	char *pc_filedir;
-	char *license;
 	char *maintainer;
-	char *copyright;
+	char *source;
+	char *license_file;
 	char *why;
 
+	pkgconf_list_t copyright;
+
+	pkgconf_list_t license;
+
+	pkgconf_list_t link_abi;
+
 	pkgconf_list_t libs;
 	pkgconf_list_t libs_private;
+	pkgconf_list_t libs_shared;
 	pkgconf_list_t cflags;
 	pkgconf_list_t cflags_private;
+	pkgconf_list_t cflags_shared;
 
 	pkgconf_list_t required;		/* this used to be requires but that is now a reserved keyword */
 	pkgconf_list_t requires_private;
+	pkgconf_list_t requires_shared;
 	pkgconf_list_t conflicts;
 	pkgconf_list_t provides;
 
@@ -177,20 +246,35 @@ struct pkgconf_pkg_ {
 
 	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;
+	pkgconf_buffer_t orig_prefix;
+	pkgconf_buffer_t calculated_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);
+
+/* iteration state threaded through pkgconf_pkg_traverse() to its callback */
+#define PKGCONF_PKG_ITERF_NONE				0x0
+#define PKGCONF_PKG_ITERF_PRIVATE			0x1
+
+typedef void (*pkgconf_pkg_traverse_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags);
 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);
+typedef const char *(*pkgconf_environ_lookup_handler_func_t)(const pkgconf_client_t *client, const char *variable);
+
+typedef struct pkgconf_client_options_ {
+	pkgconf_error_handler_func_t error_handler;
+	void *error_handler_data;
+	const pkgconf_cross_personality_t *personality;
+	void *client_data;
+	pkgconf_environ_lookup_handler_func_t environ_lookup_handler;
+	pkgconf_unveil_handler_func_t unveil_handler;
+} pkgconf_client_options_t;
 
 struct pkgconf_client_ {
 	pkgconf_list_t dir_list;
@@ -200,6 +284,7 @@ struct pkgconf_client_ {
 
 	pkgconf_list_t global_vars;
 
+	void *client_data;
 	void *error_handler_data;
 	void *warn_handler_data;
 	void *trace_handler_data;
@@ -208,6 +293,8 @@ struct pkgconf_client_ {
 	pkgconf_error_handler_func_t warn_handler;
 	pkgconf_error_handler_func_t trace_handler;
 
+	pkgconf_environ_lookup_handler_func_t environ_lookup_handler;
+
 	FILE *auditf;
 
 	char *sysroot_dir;
@@ -224,10 +311,20 @@ struct pkgconf_client_ {
 
 	pkgconf_pkg_t **cache_table;
 	size_t cache_count;
+
+	pkgconf_unveil_handler_func_t unveil_handler;
+
+	pkgconf_list_t preloaded_pkgs;
+
+	pkgconf_output_t *output;
+
+	const pkgconf_cross_personality_t *personality;
+
+	pkgconf_buffer_t _scratch_buffer;
 };
 
 struct pkgconf_cross_personality_ {
-	const char *name;
+	char *name;
 
 	pkgconf_list_t dir_list;
 
@@ -240,9 +337,52 @@ struct pkgconf_cross_personality_ {
 	bool want_default_pure;
 };
 
+/* bytecode.c */
+static inline const pkgconf_bytecode_op_t *
+pkgconf_bytecode_op_next(const pkgconf_bytecode_op_t *op)
+{
+	return (const pkgconf_bytecode_op_t *)
+		((const uint8_t *)op + sizeof(*op) + op->size);
+}
+
+typedef struct pkgconf_bytecode_eval_ctx_ {
+	const pkgconf_client_t *client;
+	const pkgconf_list_t *vars;
+
+	pkgconf_buffer_t sysroot;
+
+	size_t expansions;
+} pkgconf_bytecode_eval_ctx_t;
+
+PKGCONF_API bool pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot);
+PKGCONF_API bool pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size);
+PKGCONF_API bool pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n);
+PKGCONF_API bool pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen);
+PKGCONF_API bool pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf);
+PKGCONF_API void pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf);
+PKGCONF_API bool pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value);
+PKGCONF_API bool pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out);
+PKGCONF_API char *pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot);
+PKGCONF_API pkgconf_variable_t *pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen);
+PKGCONF_API bool pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key);
+PKGCONF_API bool pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev);
+
+/* variable.c */
+PKGCONF_API pkgconf_variable_t *pkgconf_variable_new(const char *key);
+PKGCONF_API void pkgconf_variable_free(pkgconf_variable_t *v);
+PKGCONF_API pkgconf_variable_t *pkgconf_variable_find(const pkgconf_list_t *vars, const char *key);
+PKGCONF_API pkgconf_variable_t *pkgconf_variable_get_or_create(pkgconf_list_t *vars, const char *key);
+PKGCONF_API void pkgconf_variable_delete(pkgconf_list_t *vars, pkgconf_variable_t *v);
+PKGCONF_API void pkgconf_variable_list_free(pkgconf_list_t *vars);
+PKGCONF_API bool pkgconf_variable_eval(pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_variable_t *v, pkgconf_buffer_t *out, bool *saw_sysroot);
+PKGCONF_API char *pkgconf_variable_eval_str(pkgconf_client_t *client, const pkgconf_list_t *tuples, const pkgconf_variable_t *v, bool *saw_sysroot);
+PKGCONF_API char *pkgconf_variable_eval_name(pkgconf_client_t *client, const pkgconf_list_t *vars, const char *varname);
+
 /* 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_init(pkgconf_client_t *client, pkgconf_error_handler_func_t error_handler, void *error_handler_data, const pkgconf_cross_personality_t *personality, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler);
+PKGCONF_API void pkgconf_client_init_with_options(pkgconf_client_t *client, const pkgconf_client_options_t *options);
+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, void *client_data, pkgconf_environ_lookup_handler_func_t environ_lookup_handler);
+PKGCONF_API pkgconf_client_t * pkgconf_client_new_with_options(const pkgconf_client_options_t *options);
 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);
@@ -259,7 +399,14 @@ PKGCONF_API pkgconf_error_handler_func_t
 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_one(pkgconf_client_t *client, pkgconf_pkg_t *pkg);
+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);
+PKGCONF_API void pkgconf_client_set_output(pkgconf_client_t *client, pkgconf_output_t *output);
+PKGCONF_API const char *pkgconf_client_getenv(const pkgconf_client_t *client, const char *key);
 
 /* personality.c */
 PKGCONF_API pkgconf_cross_personality_t *pkgconf_cross_personality_default(void);
@@ -278,7 +425,7 @@ PKGCONF_API void pkgconf_cross_personali
 #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
+/* 0x0100 reserved (was PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE; now threaded as a traversal parameter) */
 #define PKGCONF_PKG_PKGF_SKIP_PROVIDES			0x0200
 #define PKGCONF_PKG_PKGF_REDEFINE_PREFIX		0x0400
 #define PKGCONF_PKG_PKGF_DONT_RELOCATE_PATHS		0x0800
@@ -287,10 +434,13 @@ PKGCONF_API void pkgconf_cross_personali
 #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_PKGF_REQUIRE_INTERNAL		0x20000
+#define PKGCONF_PKG_PKGF_NO_SYSROOT_INJECTION		0x40000
 
 #define PKGCONF_PKG_DEPF_INTERNAL		0x1
 #define PKGCONF_PKG_DEPF_PRIVATE		0x2
 #define PKGCONF_PKG_DEPF_QUERY			0x4
+#define PKGCONF_PKG_DEPF_SHARED			0x8
 
 #define PKGCONF_PKG_ERRF_OK			0x0
 #define PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND	0x1
@@ -298,21 +448,23 @@ PKGCONF_API void pkgconf_cross_personali
 #define PKGCONF_PKG_ERRF_PACKAGE_CONFLICT	0x4
 #define PKGCONF_PKG_ERRF_DEPGRAPH_BREAK		0x8
 
-#if defined(__GNUC__) || defined(__INTEL_COMPILER)
-#define PRINTFLIKE(fmtarg, firstvararg) \
-        __attribute__((__format__ (__printf__, fmtarg, firstvararg)))
-#define DEPRECATED \
-        __attribute__((deprecated))
+#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)
-#define DEPRECATED
-#endif /* defined(__INTEL_COMPILER) || defined(__GNUC__) */
-
-/* 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, ...);
+# define PRINTFLIKE(fmtarg, firstvararg)
+#endif
 
-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);
+#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
 
 /* pkg.c */
 PKGCONF_API bool pkgconf_error(const pkgconf_client_t *client, const char *format, ...) PRINTFLIKE(2, 3);
@@ -323,11 +475,15 @@ PKGCONF_API bool pkgconf_default_error_h
 #ifndef PKGCONF_LITE
 #if defined(__GNUC__) || defined(__INTEL_COMPILER)
 #define PKGCONF_TRACE(client, ...) do { \
-		pkgconf_trace(client, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__); \
+		const pkgconf_client_t *pkgconf_trace_client_ = (client); \
+		if (pkgconf_trace_client_ != NULL && pkgconf_trace_client_->trace_handler != NULL) \
+			pkgconf_trace(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__); \
+		const pkgconf_client_t *pkgconf_trace_client_ = (client); \
+		if (pkgconf_trace_client_ != NULL && pkgconf_trace_client_->trace_handler != NULL) \
+			pkgconf_trace(pkgconf_trace_client_, __FILE__, __LINE__, __func__, __VA_ARGS__); \
 	} while (0)
 #endif
 #else
@@ -339,19 +495,20 @@ PKGCONF_API void pkgconf_pkg_unref(pkgco
 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_walk_conflicts_list(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *deplist);
 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 unsigned int pkgconf_pkg_link_abi(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_file(pkgconf_client_t *client, const char *path, FILE *f, unsigned int flags);
+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);
@@ -364,41 +521,70 @@ PKGCONF_API pkgconf_dependency_t *pkgcon
 
 /* argvsplit.c */
 PKGCONF_API int pkgconf_argv_split(const char *src, int *argc, char ***argv);
+PKGCONF_API int pkgconf_argv_split_raw(const char *src, int *argc, char ***argv);
 PKGCONF_API void pkgconf_argv_free(char **argv);
 
 /* fragment.c */
+typedef struct pkgconf_fragment_render_ctx_ {
+	const bool escape;
+	const char delim;
+} pkgconf_fragment_render_ctx_t;
+
 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);
+	bool (*render)(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf);
 } 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);
+
+/* A cursor accelerates repeated pkgconf_fragment_copy() calls into the same
+ * destination list by maintaining a sorted index of the fragments already
+ * present, so that the deduplication lookup is a bsearch() rather than a linear
+ * scan of the (potentially large) accumulator.
+ */
+typedef struct pkgconf_fragment_cursor_ {
+	pkgconf_list_t *list;
+	pkgconf_fragment_t **index;
+	size_t count;
+	size_t alloc;
+} pkgconf_fragment_cursor_t;
+
+PKGCONF_API bool pkgconf_fragment_parse(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, const char *value, unsigned int flags);
+PKGCONF_API void pkgconf_fragment_insert(pkgconf_client_t *client, pkgconf_list_t *list, char type, const char *data, bool tail);
+PKGCONF_API bool pkgconf_fragment_add(pkgconf_client_t *client, pkgconf_list_t *list, pkgconf_list_t *vars, 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_cursor_init(pkgconf_fragment_cursor_t *cursor, pkgconf_list_t *list);
+PKGCONF_API void pkgconf_fragment_cursor_deinit(pkgconf_fragment_cursor_t *cursor);
+PKGCONF_API void pkgconf_fragment_copy_cursor(const pkgconf_client_t *client, pkgconf_fragment_cursor_t *cursor, 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 void pkgconf_fragment_filter_splice(const pkgconf_client_t *client, pkgconf_list_t *dest, pkgconf_list_t *src, pkgconf_fragment_filter_func_t filter_func, void *data);
+PKGCONF_API bool pkgconf_fragment_render_buf(const pkgconf_list_t *list, pkgconf_buffer_t *buf, bool escape, const pkgconf_fragment_render_ops_t *ops, char delim);
 PKGCONF_API bool pkgconf_fragment_has_system_dir(const pkgconf_client_t *client, const pkgconf_fragment_t *frag);
+PKGCONF_API bool pkgconf_is_locale_utf8(void);
+
+/* license.c */
+PKGCONF_API bool pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base);
+PKGCONF_API bool pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *deplist_head, const char *expression, unsigned int flags);
+PKGCONF_API bool pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *deplist, const char *depends, unsigned int flags);
+PKGCONF_API void pkgconf_license_free(pkgconf_list_t *list);
+PKGCONF_API bool pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data);
+PKGCONF_API bool pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf);
 
 /* 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 const char *pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key);
 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 const char *pkgconf_tuple_find_global(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 void pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep);
 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);
@@ -421,31 +607,126 @@ PKGCONF_API void pkgconf_audit_log_depen
 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);
+PKGCONF_API size_t pkgconf_path_build_from_environ(const pkgconf_client_t *client, const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter);
+#ifdef _WIN32
+PKGCONF_API size_t pkgconf_path_build_from_registry(pkgconf_client_t *client, /* 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 bool pkgconf_path_relocate(pkgconf_buffer_t *buf);
+PKGCONF_API void pkgconf_path_normalize_separators(char *path);
 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);
+PKGCONF_API bool pkgconf_path_is_plausible(const pkgconf_buffer_t *buf);
 
 /* buffer.c */
-typedef struct pkgconf_buffer_ {
-	char *base;
-	char *end;
-} pkgconf_buffer_t;
+struct pkgconf_span_ {
+	unsigned char lo;
+	unsigned char hi;	/* inclusive */
+};
+
+static inline bool pkgconf_span_contains(unsigned char c, const pkgconf_span_t *spans, size_t nspans) {
+	for (size_t i = 0; i < nspans; i++)
+		if (c >= spans[i].lo && c <= spans[i].hi)
+			return true;
+
+	return false;
+}
+
+/* A set of byte values as a 256-bit map, so that membership is a single test
+ * rather than a walk over a span list.  Build one with
+ * pkgconf_charset_from_spans() and keep it: the point is to hoist the span
+ * walk out of any per-byte loop. */
+typedef struct pkgconf_charset_ {
+	uint64_t words[4];
+} pkgconf_charset_t;
+
+static inline bool pkgconf_charset_contains(const pkgconf_charset_t *charset, unsigned char c)
+{
+	return (charset->words[c >> 6] >> (c & 63)) & 1;
+}
+
+PKGCONF_API void pkgconf_charset_from_spans(pkgconf_charset_t *charset, const pkgconf_span_t *spans, size_t nspans);
 
-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 bool pkgconf_buffer_append(pkgconf_buffer_t *buffer, const char *text);
+PKGCONF_API bool pkgconf_buffer_append_slice(pkgconf_buffer_t *buf, const char *p, size_t n);
+PKGCONF_API bool pkgconf_buffer_append_fmt(pkgconf_buffer_t *buffer, const char *fmt, ...) PRINTFLIKE(2, 3);
+PKGCONF_API bool pkgconf_buffer_append_vfmt(pkgconf_buffer_t *buffer, const char *fmt, va_list va) PRINTFLIKE(2, 0);
+PKGCONF_API bool pkgconf_buffer_prepend(pkgconf_buffer_t *buffer, const char *text);
+PKGCONF_API bool pkgconf_buffer_push_byte(pkgconf_buffer_t *buffer, char byte);
+PKGCONF_API bool 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) {
+PKGCONF_API bool pkgconf_buffer_fputs(pkgconf_buffer_t *buffer, FILE *out);
+PKGCONF_API bool pkgconf_buffer_vjoin(pkgconf_buffer_t *buffer, char delim, va_list va);
+PKGCONF_API bool pkgconf_buffer_join(pkgconf_buffer_t *buffer, int delim, ...);
+PKGCONF_API bool pkgconf_buffer_has_prefix(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *prefix);
+PKGCONF_API bool pkgconf_buffer_contains(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle);
+PKGCONF_API bool pkgconf_buffer_contains_byte(const pkgconf_buffer_t *haystack, char needle);
+PKGCONF_API bool pkgconf_buffer_match(const pkgconf_buffer_t *haystack, const pkgconf_buffer_t *needle);
+PKGCONF_API bool pkgconf_buffer_subst(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pattern, const char *value);
+PKGCONF_API bool pkgconf_buffer_escape(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_span_t *spans, size_t nspans);
+PKGCONF_API bool pkgconf_buffer_escape_charset(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const pkgconf_charset_t *charset);
+
+/*
+ * !doc
+ *
+ * .. c:function:: static inline const char *pkgconf_buffer_str(const pkgconf_buffer_t *buffer)
+ *
+ *    Get the underlying string from the buffer. This may return :code:`NULL`.
+ *
+ *    :param const pkgconf_buffer_t *buffer: The buffer to get the string from.
+ *    :return: The underlying string.
+ */
+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) {
+/*
+ * !doc
+ *
+ * .. c:function:: static inline const char *pkgconf_buffer_str_or_empty(const pkgconf_buffer_t *buffer)
+ *
+ *    Get the underlying string from the buffer, or the empty string if :code:`NULL`.
+ *
+ *    :param const pkgconf_buffer_t *buffer: The buffer to get the string from.
+ *    :return: The underlying string, or the empty string.
+ */
+static inline const char *pkgconf_buffer_str_or_empty(const pkgconf_buffer_t *buffer)
+{
+	return buffer->base != NULL ? buffer->base : "";
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: static inline size_t *pkgconf_buffer_len(const pkgconf_buffer_t *buffer)
+ *
+ *    Get the underlying raw buffer length.
+ *
+ *    :param const pkgconf_buffer_t *buffer: The buffer to check the length of.
+ *    :return: The size of the underlying buffer.
+ */
+static inline size_t pkgconf_buffer_len(const pkgconf_buffer_t *buffer)
+{
+	if (buffer->base == NULL)
+		return 0;
+
 	return (size_t)(ptrdiff_t)(buffer->end - buffer->base);
 }
 
-static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer) {
+/*
+ * !doc
+ *
+ * .. c:function:: static inline const char *pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer)
+ *
+ *    Get the last character from the buffer. If the buffer is empty, return :code:`'\0'`.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to get the last character from.
+ *    :return: The last character, or '\0' if the string is empty.
+ */
+static inline char pkgconf_buffer_lastc(const pkgconf_buffer_t *buffer)
+{
 	if (buffer->base == buffer->end)
 		return '\0';
 
@@ -453,14 +734,140 @@ static inline char pkgconf_buffer_lastc(
 }
 
 #define PKGCONF_BUFFER_INITIALIZER { NULL, NULL }
+#define PKGCONF_BUFFER_FROM_STR(str) &(const pkgconf_buffer_t){ .base = str, .end = ((str) ? &(str)[strlen(str)] : (str)) }
+#define PKGCONF_BUFFER_FROM_STR_NONNULL(str) &(const pkgconf_buffer_t){ .base = str, .end = &(str)[strlen(str)] }
 
-static inline void pkgconf_buffer_reset(pkgconf_buffer_t *buffer) {
+/*
+ * !doc
+ *
+ * .. c:function:: static inline void pkgconf_buffer_reset(const pkgconf_buffer_t *buffer)
+ *
+ *    Reset the underlying buffer, freeing any existing string.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to reset.
+ *    :return: nothing
+ */
+static inline void pkgconf_buffer_reset(pkgconf_buffer_t *buffer)
+{
 	pkgconf_buffer_finalize(buffer);
 	buffer->base = buffer->end = NULL;
 }
 
+/*
+ * !doc
+ *
+ * .. c:function:: static inline void pkgconf_buffer_rewind(pkgconf_buffer_t *buffer)
+ *
+ *    Truncate the buffer to empty while retaining its allocation, so it can be
+ *    refilled without reallocating.  Unlike pkgconf_buffer_reset(), the backing
+ *    storage is kept.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to rewind.
+ *    :return: nothing
+ */
+static inline void pkgconf_buffer_rewind(pkgconf_buffer_t *buffer)
+{
+	if (buffer->base != NULL)
+	{
+		buffer->end = buffer->base;
+		*buffer->base = '\0';
+	}
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: static inline char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer)
+ *
+ *    Hand ownership of the underlying storage to the caller as a string and
+ *    empty the buffer.  The string must be freed by the caller.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to freeze.
+ *    :return: The underlying string.
+ */
+static inline char *pkgconf_buffer_freeze(pkgconf_buffer_t *buffer)
+{
+	char *out = buffer->base;
+
+	buffer->base = buffer->end = NULL;
+
+	return out;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: static inline bool pkgconf_buffer_copy(pkgconf_buffer_t *buffer, pkgconf_buffer_t *newptr)
+ *
+ *    Copy the contents of one buffer to another, erasing the contents of the buffer in :code:`newptr`.
+ *    The source and destination buffers must not overlap.
+ *
+ *    :param pkgconf_buffer_t *buffer: The buffer to copy from.
+ *    :param pkgconf_buffer_t *buffer: The buffer to copy to.
+ *    :return: :code:`true` on success, :code:`false` on failure.
+ */
+static inline bool pkgconf_buffer_copy(pkgconf_buffer_t *buffer, pkgconf_buffer_t *newptr)
+{
+	if (buffer == newptr || (buffer->base != NULL && buffer->base == newptr->base))
+		return false;
+
+	pkgconf_buffer_reset(newptr);
+	return pkgconf_buffer_append_slice(newptr, pkgconf_buffer_str(buffer), pkgconf_buffer_len(buffer));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: static inline bool pkgconf_str_eq_slice(const char *s, const char *p, size_t n)
+ *
+ *    Compare :code:`s` to :code:`p`, which must be :code:`n` size long.
+ *
+ *    :param const char *s: string to search
+ *    :param const char *p: string to search for
+ *    :param size_t n: length of :code:`s`
+ *    :return: :code:`true` if equal, :code:`false` if not.
+ */
+static inline bool pkgconf_str_eq_slice(const char *s, const char *p, size_t n)
+{
+	return s != NULL &&
+		strncmp(s, p, n) == 0 &&
+		s[n] == '\0';
+}
+
+/* bufferset.c */
+PKGCONF_API pkgconf_bufferset_t *pkgconf_bufferset_extend(pkgconf_list_t *list, pkgconf_buffer_t *buffer);
+PKGCONF_API void pkgconf_bufferset_free(pkgconf_list_t *list);
+
 /* fileio.c */
 PKGCONF_API bool pkgconf_fgetline(pkgconf_buffer_t *buffer, FILE *stream);
+
+/* parser.c */
+typedef void (*pkgconf_parser_operand_func_t)(void *data, const char *warnprefix, const char *key, const char *value);
+typedef void (*pkgconf_parser_warn_func_t)(void *data, const char *fmt, ...);
+
+PKGCONF_API void pkgconf_parser_parse_buffer(void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, pkgconf_buffer_t *buffer, const char *warnprefix);
+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);
+
+/* output.c */
+typedef enum {
+	PKGCONF_OUTPUT_STDOUT,
+	PKGCONF_OUTPUT_STDERR,
+} pkgconf_output_stream_t;
+
+struct pkgconf_output_ {
+	void *privdata;
+
+	bool (*write)(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer);
+};
+
+PKGCONF_API bool pkgconf_output_putbuf(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer, bool newline);
+PKGCONF_API bool pkgconf_output_puts(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *str);
+PKGCONF_API bool pkgconf_output_fmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, ...) PRINTFLIKE(3,4);
+PKGCONF_API bool pkgconf_output_vfmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, va_list va_src) PRINTFLIKE(3,0);
+PKGCONF_API pkgconf_output_t *pkgconf_output_default(void);
+
+PKGCONF_API bool pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va) PRINTFLIKE(2,0);
+PKGCONF_API bool pkgconf_output_file_fmt(FILE *f, const char *fmt, ...) PRINTFLIKE(2,3);
 
 #ifdef __cplusplus
 }
Index: libpkgconf/license.c
===================================================================
RCS file: libpkgconf/license.c
diff -N libpkgconf/license.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/license.c	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,373 @@
+/*
+ * license.c
+ * Evaluate SPDX license expressions
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 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/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+static bool license_sanitize_string(const char *s, pkgconf_buffer_t *buf)
+{
+	unsigned int i = 0;
+	/*
+	 * Allowed chars are:
+	 * - a-z
+	 * - 0-9
+	 * - chars '-', '+', '.', '(' and ')'
+	 */
+	for (i = 0; i < strlen(s); i ++)
+	{
+		if (isalnum((unsigned char) s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '(' || s[i] == ')' || s[i] == '.' || s[i] == ':')
+		{
+			if (!pkgconf_buffer_push_byte(buf, s[i]))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
+ *
+ *    Copies a `license list` to another `license list`
+ *
+ *    :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: true on success, false on allocation failure
+ */
+bool
+pkgconf_license_copy_list(const pkgconf_client_t *client, pkgconf_list_t *list, const pkgconf_list_t *base)
+{
+	pkgconf_node_t *node;
+	(void) client;
+
+	PKGCONF_FOREACH_LIST_ENTRY(base->head, node)
+	{
+		pkgconf_license_t *license = node->data;
+		pkgconf_license_t *cpy_license = calloc(1, sizeof(pkgconf_license_t));
+
+		if (cpy_license == NULL)
+			return false;
+
+		cpy_license->type = license->type;
+
+		if (license->data != NULL)
+		{
+			cpy_license->data = strdup(license->data);
+			if (cpy_license->data == NULL)
+			{
+				free(cpy_license);
+				return false;
+			}
+		}
+
+		pkgconf_node_insert_tail(&cpy_license->iter, cpy_license, list);
+	}
+
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_license_free(pkgconf_list_t *list)
+ *
+ *    Delete an entire `license list`.
+ *
+ *    :param pkgconf_list_t* list: The `license list` to delete.
+ *    :return: nothing
+ */
+void
+pkgconf_license_free(pkgconf_list_t *list)
+{
+	if (!list)
+		return;
+
+	pkgconf_node_t *node, *next;
+
+	PKGCONF_FOREACH_LIST_ENTRY_SAFE(list->head, next, node)
+	{
+		pkgconf_license_t *license = node->data;
+		free(license->data);
+		free(license);
+	}
+
+	pkgconf_list_zero(list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
+ *
+ *    Adds a `license` of text to a `license 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 license.
+ *    :param char* data: The data of the license
+ *    :return: true on success, false on allocation failure
+ */
+bool
+pkgconf_license_insert(pkgconf_client_t *client, pkgconf_list_t *list, unsigned char type, const char *data)
+{
+	pkgconf_license_t *license;
+
+	if (data == NULL)
+		return false;
+
+	license = calloc(1, sizeof(pkgconf_license_t));
+	if (!license)
+	{
+		pkgconf_error(client, "pkgconf_license_insert: out of memory");
+		return false;
+	}
+	license->type = type;
+	license->data = strdup(data);
+	if (license->data == NULL)
+	{
+		pkgconf_error(client, "pkgconf_license_insert: out of memory");
+		free(license);
+		return false;
+	}
+
+	pkgconf_node_insert_tail(&license->iter, license, list);
+	return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
+ *
+ * Evaluates SPDX expression strings like:
+ * - BSD-3-Clause
+ * - LGPL-2.1-only OR MIT
+ * - LGPL-2.1-only OR MIT OR BSD-3-Clause
+ * - ISC AND (BSD-3-Clause AND BSD-2-Clause)
+ *
+ * Function parses and sanitizes license strings. Also adding multiple
+ * 'License:'-keys is supported like:
+ * License: BSD-3-Clause
+ * License: BSD-2-Clause
+ *
+ * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
+ *
+ * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
+ * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
+ * :param char* expression: The dependency data to parse.
+ * :param uint flags: Any flags to attach to the dependency nodes.
+ * :return: true on success, false on allocation failure
+ */
+bool
+pkgconf_license_evaluate_str(pkgconf_client_t *client, pkgconf_list_t *license_list, const char *expression, unsigned int flags)
+{
+	pkgconf_buffer_t out_buffer = PKGCONF_BUFFER_INITIALIZER;
+	size_t buf_size = 0;
+	char *cur_word = NULL;
+	int i, ret, argc;
+	char **argv;
+	size_t string_len = 0;
+
+	(void)flags;
+
+	if (expression == NULL)
+		return true;
+
+	buf_size = strlen(expression) + 1;
+	ret = pkgconf_argv_split(expression, &argc, &argv);
+	if (ret)
+		return true;
+
+	/* This is not the first License:
+	 * so add AND
+	 */
+	if (license_list->head)
+	{
+		if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND"))
+			goto fail;
+	}
+
+	i = 0;
+	while (i < argc)
+	{
+		string_len = strnlen(argv[i], buf_size);
+		cur_word = argv[i];
+		if (string_len >= 1)
+		{
+			if (!license_sanitize_string(cur_word, &out_buffer))
+				goto fail;
+
+			if (!pkgconf_buffer_len(&out_buffer))
+			{
+				i ++;
+				pkgconf_buffer_finalize(&out_buffer);
+				continue;
+			}
+
+			cur_word = (char *)pkgconf_buffer_str(&out_buffer);
+			size_t cur_len = strnlen(cur_word, buf_size);
+			if (!cur_len)
+			{
+				i ++;
+				pkgconf_buffer_finalize(&out_buffer);
+				continue;
+			}
+
+			if (cur_word[0] == '(')
+			{
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_OPEN, "("))
+					goto fail;
+				/* If there is more after '(' like '(BSD-2-Clause'
+				 * Then append rest to fragments as license.
+				 * This is expression like GPL-2.0-only OR (BSD-2-Clause AND ISC)
+				 */
+				if (cur_len >= 2)
+				{
+					cur_word ++;
+					if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
+						goto fail;
+				}
+			}
+			else if (cur_word[cur_len - 1] == ')')
+			{
+				if (cur_len >= 2)
+				{
+					cur_word[cur_len - 1] = 0x00;
+					if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
+						goto fail;
+				}
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_BRACKET_CLOSE, ")"))
+					goto fail;
+			}
+			else if (!strcasecmp(cur_word, "and"))
+			{
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_AND, "AND"))
+					goto fail;
+			}
+			else if (!strcasecmp(cur_word, "or"))
+			{
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_OR, "OR"))
+					goto fail;
+			}
+			else if (!strcasecmp(cur_word, "with"))
+			{
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_WITH, "WITH"))
+					goto fail;
+			}
+			else
+			{
+				if (!pkgconf_license_insert(client, license_list, PKGCONF_LICENSE_EXPRESSION, cur_word))
+					goto fail;
+			}
+			pkgconf_buffer_finalize(&out_buffer);
+		}
+		i++;
+	}
+
+	pkgconf_argv_free(argv);
+	return true;
+
+fail:
+	pkgconf_buffer_finalize(&out_buffer);
+	pkgconf_argv_free(argv);
+	return false;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags)
+ *
+ * Evaluates SPDX expression strings like:
+ * - BSD-3-Clause
+ * - LGPL-2.1-only OR MIT
+ * - LGPL-2.1-only OR MIT OR BSD-3-Clause
+ * - ISC AND (BSD-3-Clause AND BSD-2-Clause)
+ *
+ * Function parses and sanitizes license strings. Also adding multiple
+ * 'License:'-keys is supported like:
+ * License: BSD-3-Clause
+ * License: BSD-2-Clause
+ *
+ * Which will evaluate to: BSD-3-Clause AND BSD-2-Clause
+ *
+ * :param pkgconf_client_t* client: The client object that owns the package this dependency list belongs to.
+ * :param pkgconf_list_t* license_list: The dependency list to populate with dependency nodes.
+ * :param char* expression: The dependency data to parse.
+ * :param uint flags: Any flags to attach to the dependency nodes.
+ * :return: true on success, false on allocation failure
+ */
+bool
+pkgconf_license_evaluate(pkgconf_client_t *client, pkgconf_pkg_t *pkg, pkgconf_list_t *license_list, const char *license_str, unsigned int flags)
+{
+	char *license_expression = pkgconf_bytecode_eval_str(client, &pkg->vars, license_str, NULL);
+	if (license_expression == NULL)
+		return false;
+
+	bool ret = pkgconf_license_evaluate_str(client, license_list, license_expression, flags);
+	free(license_expression);
+	return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_dependency_parse_str(pkgconf_list_t *deplist_head, const char *depends)
+ *
+ * Renders license fragments back to SPDX expression string. Tries
+ * to keep output as close as possible to original input
+ *
+ * :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
+ */
+bool
+pkgconf_license_render(pkgconf_client_t *client, const pkgconf_list_t *list, pkgconf_buffer_t *buf)
+{
+	const pkgconf_buffer_t *frag_string = NULL;
+	pkgconf_node_t *node;
+	bool is_delim = true;
+
+	(void)client;
+
+	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
+	{
+		pkgconf_license_t *license = node->data;
+		is_delim = true;
+		frag_string = PKGCONF_BUFFER_FROM_STR(license->data);
+		if (!pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(frag_string)))
+			return false;
+
+		if (license->type == PKGCONF_LICENSE_BRACKET_OPEN || (node->next != NULL && ((const pkgconf_license_t *)node->next->data)->type == PKGCONF_LICENSE_BRACKET_CLOSE))
+		{
+			is_delim = false;
+		}
+
+		if (node->next != NULL && is_delim)
+		{
+			if (!pkgconf_buffer_push_byte(buf, ' '))
+				return false;
+		}
+	}
+
+	return true;
+}
Index: libpkgconf/output.c
===================================================================
RCS file: libpkgconf/output.c
diff -N libpkgconf/output.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/output.c	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,163 @@
+/*
+ * output.c
+ * I/O abstraction layer
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * 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.
+ */
+
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+bool
+pkgconf_output_putbuf(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer, bool newline)
+{
+	bool ret;
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (pkgconf_buffer_len(buffer) != 0 &&
+		!pkgconf_buffer_append(&buf, pkgconf_buffer_str(buffer)))
+		return false;
+
+	if (newline && !pkgconf_buffer_push_byte(&buf, '\n'))
+	{
+		pkgconf_buffer_finalize(&buf);
+		return false;
+	}
+
+	ret = output->write(output, stream, &buf);
+	pkgconf_buffer_finalize(&buf);
+
+	return ret;
+}
+
+bool
+pkgconf_output_puts(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *str)
+{
+	bool ret;
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!pkgconf_buffer_append(&buf, str) ||
+		!pkgconf_buffer_push_byte(&buf, '\n'))
+	{
+		pkgconf_buffer_finalize(&buf);
+		return false;
+	}
+
+	ret = output->write(output, stream, &buf);
+	pkgconf_buffer_finalize(&buf);
+
+	return ret;
+}
+
+bool
+pkgconf_output_fmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, ...)
+{
+	bool ret;
+	va_list va;
+
+	va_start(va, fmt);
+	ret = pkgconf_output_vfmt(output, stream, fmt, va);
+	va_end(va);
+
+	return ret;
+}
+
+bool
+pkgconf_output_vfmt(pkgconf_output_t *output, pkgconf_output_stream_t stream, const char *fmt, va_list src_va)
+{
+	va_list va;
+	bool ret;
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+	va_copy(va, src_va);
+	ret = pkgconf_buffer_append_vfmt(&buf, fmt, va);
+	va_end(va);
+
+	if (ret)
+		ret = output->write(output, stream, &buf);
+	pkgconf_buffer_finalize(&buf);
+
+	return ret;
+}
+
+static bool
+pkgconf_output_stdio_write(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer)
+{
+	(void) output;
+
+	FILE *target = stream == PKGCONF_OUTPUT_STDERR ? stderr : stdout;
+
+	if (buffer != NULL)
+	{
+		const char *str = pkgconf_buffer_str(buffer);
+		size_t size = pkgconf_buffer_len(buffer);
+
+		if (size > 0 && !fwrite(str, size, 1, target))
+			return false;
+	}
+
+	fflush(target);
+	return true;
+}
+
+static pkgconf_output_t pkgconf_default_output = {
+	.privdata = NULL,
+	.write = pkgconf_output_stdio_write,
+};
+
+pkgconf_output_t *
+pkgconf_output_default(void)
+{
+	return &pkgconf_default_output;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va)
+ *
+ *    Wrapper around :code:`vfprintf` that returns a boolean.
+ *
+ *    :param FILE *f: Pointer to an open `FILE` pointer.
+ *    :param: const char *fmt: Format string.
+ *    :param va_list va: Variable list.
+ *    :return: :code:`true` on success, :code:`false` on failure.
+ */
+bool
+pkgconf_output_file_vfmt(FILE *f, const char *fmt, va_list va)
+{
+	int ret = vfprintf(f, fmt, va);
+	return ret >= 0;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_output_file_fmt(FILE *f, const char *fmt, va_list va)
+ *
+ *    Wrapper around :code:`fprintf` that returns a boolean.
+ *
+ *    :param FILE *f: Pointer to an open `FILE` pointer.
+ *    :param: const char *fmt: Format string.
+ *    :return: :code:`true` on success, :code:`false` on failure.
+ */
+bool
+pkgconf_output_file_fmt(FILE *f, const char *fmt, ...)
+{
+	va_list va;
+	va_start(va, fmt);
+	bool ret = pkgconf_output_file_vfmt(f, fmt, va);
+	va_end(va);
+
+	return ret;
+}
Index: libpkgconf/parser.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/parser.c,v
diff -u -p -r1.1.1.1 parser.c
--- libpkgconf/parser.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/parser.c	4 Aug 2026 16:30:47 -0000
@@ -2,7 +2,9 @@
  * parser.c
  * rfc822 message parser
  *
- * Copyright (c) 2018 pkgconf authors (see AUTHORS).
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2018, 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
@@ -17,100 +19,134 @@
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
 
-/*
- * !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)
+static void
+pkgconf_parser_canonicalize_line(pkgconf_buffer_t *buffer)
 {
-	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++;
+	bool escaped = false;
+	char *buf = buffer->base;
+	char *src = buf, *dst = buf;
 
-		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 (buf == NULL)
+		return;
 
-		if (!isalpha((unsigned char)*key) &&
-		    !isdigit((unsigned char)*p))
+	for (; *src != '\0'; src++)
+	{
+		if (*src == '\\' && !escaped)
 		{
-			pkgconf_buffer_reset(&readbuf);
+			escaped = true;
 			continue;
 		}
 
-		while (*p && isspace((unsigned char)*p))
+		if (*src == '#' && !escaped)
+			break;
+
+		if (escaped)
 		{
-			if (!warned_key_whitespace)
+			if (*src == '#')
 			{
-				warnfunc(data, "%s:" SIZE_FMT_SPECIFIER ": warning: whitespace encountered while parsing key section\n",
-					filename, lineno);
-				warned_key_whitespace = true;
+				*dst++ = '#';
+				escaped = false;
+				continue;
 			}
 
-			/* set to null to avoid trailing spaces in key */
-			*p = '\0';
-			p++;
+			*dst++ = '\\';
+			escaped = false;
 		}
 
-		op = *p;
-		if (*p != '\0')
+		*dst++ = *src;
+	}
+
+	if (escaped)
+		*dst++ = '\\';
+
+	*dst = '\0';
+	buffer->end = dst;
+}
+
+void
+pkgconf_parser_parse_buffer(void *data, const pkgconf_parser_operand_func_t *ops, const pkgconf_parser_warn_func_t warnfunc, pkgconf_buffer_t *buffer, const char *warnprefix)
+{
+	char op, *p, *key, *value;
+	size_t vallen;
+
+	pkgconf_parser_canonicalize_line(buffer);
+
+	p = buffer->base;
+	if (p == NULL)
+		return;
+	while (*p && isspace((unsigned char)*p))
+		p++;
+	if (*p && p != buffer->base)
+	{
+		warnfunc(data, "%s: warning: whitespace encountered while parsing key section\n",
+			warnprefix);
+	}
+	key = p;
+	while (*p && (isalpha((unsigned char)*p) || isdigit((unsigned char)*p) || *p == '_' || *p == '.'))
+		p++;
+
+	if (!isalpha((unsigned char)*key) && !isdigit((unsigned char)*key))
+		return;
+
+	while (*p && isspace((unsigned char)*p))
+	{
+		warnfunc(data, "%s: warning: whitespace encountered while parsing key section\n",
+			warnprefix);
+
+		/* 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;
+	vallen = strlen(value);
+	if (vallen)
+		p = value + (vallen - 1);
+
+	while (*p && isspace((unsigned char) *p) && p > value)
+	{
+		if (op == '=')
 		{
-			*p = '\0';
-			p++;
+			warnfunc(data, "%s: warning: trailing whitespace encountered while parsing value section\n",
+				warnprefix);
 		}
 
-		while (*p && isspace((unsigned char)*p))
-			p++;
+		*p = '\0';
+		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;
-			}
+	if (ops[(unsigned char) op])
+		ops[(unsigned char) op](data, warnprefix, key, value);
+}
 
-			*p = '\0';
-			p--;
-		}
-		if (ops[(unsigned char) op])
-			ops[(unsigned char) op](data, lineno, key, value);
+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 warnprefix[PKGCONF_ITEM_SIZE];
+
+		continue_reading = pkgconf_fgetline(&readbuf, f);
+		lineno++;
 
-		pkgconf_buffer_reset(&readbuf);
+		snprintf(warnprefix, sizeof warnprefix, "%s:" SIZE_FMT_SPECIFIER, filename, lineno);
+		pkgconf_parser_parse_buffer(data, ops, warnfunc, &readbuf, warnprefix);
+		pkgconf_buffer_rewind(&readbuf);
 	}
 
-	fclose(f);
 	pkgconf_buffer_finalize(&readbuf);
 }
Index: libpkgconf/path.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/path.c,v
diff -u -p -r1.1.1.1 path.c
--- libpkgconf/path.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/path.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * path.c
  * filesystem path management
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2016 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -16,6 +18,7 @@
 #include <libpkgconf/config.h>
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/path.h>
 
 #if defined(HAVE_SYS_STAT_H) && ! defined(_WIN32)
 # include <sys/stat.h>
@@ -24,9 +27,9 @@
 
 static bool
 #ifdef PKGCONF_CACHE_INODES
-path_list_contains_entry(const char *text, pkgconf_list_t *dirlist, struct stat *st)
+path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist, struct stat *st)
 #else
-path_list_contains_entry(const char *text, pkgconf_list_t *dirlist)
+path_list_contains_entry(const pkgconf_buffer_t *text, pkgconf_list_t *dirlist)
 #endif
 {
 	pkgconf_node_t *n;
@@ -40,7 +43,7 @@ path_list_contains_entry(const char *tex
 			return true;
 #endif
 
-		if (!strcmp(text, pn->path))
+		if (!strcmp(pkgconf_buffer_str(text), pn->path))
 			return true;
 	}
 
@@ -62,39 +65,71 @@ 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_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!pkgconf_buffer_append(&pathbuf, text))
+		return NULL;
 
-	pkgconf_strlcpy(path, text, sizeof path);
-	pkgconf_path_relocate(path, sizeof path);
+	if (!pkgconf_path_relocate(&pathbuf))
+	{
+		pkgconf_buffer_finalize(&pathbuf);
+		return NULL;
+	}
 
 #ifdef PKGCONF_CACHE_INODES
 	struct stat st;
 
 	if (filter)
 	{
-		if (lstat(path, &st) == -1)
+		if (lstat(pkgconf_buffer_str(&pathbuf), &st) == -1)
+		{
+			pkgconf_buffer_finalize(&pathbuf);
 			return NULL;
+		}
+
 		if (S_ISLNK(st.st_mode))
 		{
-			char pathbuf[PKGCONF_ITEM_SIZE * 4];
-			char *linkdest = realpath(path, pathbuf);
+			char realpathbuf[PKGCONF_ITEM_SIZE * 4];
+			char *linkdest = realpath(pkgconf_buffer_str(&pathbuf), realpathbuf);
 
 			if (linkdest != NULL && stat(linkdest, &st) == -1)
+			{
+				pkgconf_buffer_finalize(&pathbuf);
 				return NULL;
+			}
 		}
-		if (path_list_contains_entry(path, dirlist, &st))
+
+		if (path_list_contains_entry(&pathbuf, dirlist, &st))
+		{
+			pkgconf_buffer_finalize(&pathbuf);
 			return NULL;
+		}
 	}
 #else
-	if (filter && path_list_contains_entry(path, dirlist))
+	if (filter && path_list_contains_entry(&pathbuf, dirlist))
+	{
+		pkgconf_buffer_finalize(&pathbuf);
 		return NULL;
+	}
 #endif
 
 	node = calloc(1, sizeof(pkgconf_path_t));
-	node->path = strdup(path);
+	if (node == NULL)
+	{
+		pkgconf_buffer_finalize(&pathbuf);
+		return NULL;
+	}
+
+	node->path = pkgconf_buffer_freeze(&pathbuf);
+	if (node->path == NULL)
+	{
+		free(node);
+		return NULL;
+	}
 
 #ifdef PKGCONF_CACHE_INODES
-	if (filter) {
+	if (filter)
+	{
 		node->handle_path = (void *)(intptr_t) st.st_ino;
 		node->handle_device = (void *)(intptr_t) st.st_dev;
 	}
@@ -170,6 +205,9 @@ pkgconf_path_split(const char *text, pkg
 		return 0;
 
 	iter = workbuf = strdup(text);
+	if (workbuf == NULL)
+		return 0;
+
 	while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL)
 	{
 		pkgconf_path_add(p, dirlist, filter);
@@ -189,6 +227,7 @@ pkgconf_path_split(const char *text, pkg
  *    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 pkgconf_client_t* client: The client to use for environmental variable lookup (can be NULL).
  *    :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.
@@ -197,11 +236,11 @@ pkgconf_path_split(const char *text, pkg
  *    :rtype: size_t
  */
 size_t
-pkgconf_path_build_from_environ(const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter)
+pkgconf_path_build_from_environ(const pkgconf_client_t *client, const char *envvarname, const char *fallback, pkgconf_list_t *dirlist, bool filter)
 {
 	const char *data;
 
-	data = getenv(envvarname);
+	data = pkgconf_client_getenv(client, envvarname);
 	if (data != NULL)
 		return pkgconf_path_split(data, dirlist, filter);
 
@@ -228,21 +267,38 @@ bool
 pkgconf_path_match_list(const char *path, const pkgconf_list_t *dirlist)
 {
 	pkgconf_node_t *n = NULL;
-	char relocated[PKGCONF_ITEM_SIZE];
+	pkgconf_buffer_t relocated = PKGCONF_BUFFER_INITIALIZER;
 	const char *cpath = path;
 
-	pkgconf_strlcpy(relocated, path, sizeof relocated);
-	if (pkgconf_path_relocate(relocated, sizeof relocated))
-		cpath = relocated;
+	if (path == NULL)
+		return false;
+
+	if (!pkgconf_buffer_append(&relocated, path))
+		return false;
+
+	cpath = pkgconf_buffer_str(&relocated);
+
+	if (pkgconf_path_relocate(&relocated))
+		cpath = pkgconf_buffer_str(&relocated);
+
+	if (cpath == NULL)
+	{
+		pkgconf_buffer_finalize(&relocated);
+		return false;
+	}
 
 	PKGCONF_FOREACH_LIST_ENTRY(dirlist->head, n)
 	{
 		pkgconf_path_t *pnode = n->data;
 
 		if (!strcmp(pnode->path, cpath))
+		{
+			pkgconf_buffer_finalize(&relocated);
 			return true;
+		}
 	}
 
+	pkgconf_buffer_finalize(&relocated);
 	return false;
 }
 
@@ -267,7 +323,15 @@ pkgconf_path_copy_list(pkgconf_list_t *d
 		pkgconf_path_t *srcpath = n->data, *path;
 
 		path = calloc(1, sizeof(pkgconf_path_t));
+		if (path == NULL)
+			continue;
+
 		path->path = strdup(srcpath->path);
+		if (path->path == NULL)
+		{
+			free(path);
+			continue;
+		}
 
 #ifdef PKGCONF_CACHE_INODES
 		path->handle_path = srcpath->handle_path;
@@ -281,6 +345,46 @@ pkgconf_path_copy_list(pkgconf_list_t *d
 /*
  * !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);
+		if (path->path == NULL)
+		{
+			free(path);
+			continue;
+		}
+
+#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.
@@ -304,62 +408,242 @@ pkgconf_path_free(pkgconf_list_t *dirlis
 	pkgconf_list_zero(dirlist);
 }
 
-static char *
-normpath(const char *path)
+/*
+ * !doc
+ *
+ * .. c:function:: void pkgconf_path_normalize_separators(char *path)
+ *
+ *    Rewrites a real OS path into pkgconf's canonical internal form, in which
+ *    the platform directory separator is always represented as ``/``.  On POSIX
+ *    this is a no-op (a backslash is an ordinary filename character); on Windows
+ *    it maps ``\`` to ``/``.  Runtime-injected paths (search paths, sysroot,
+ *    build root, prefix, pcfiledir) are normalized this way so that later path
+ *    comparisons -- sysroot injection and system-directory filtering -- are all
+ *    made on a consistent basis.
+ *
+ *    :param char* path: The path to normalize in place.
+ *    :return: nothing
+ */
+void
+pkgconf_path_normalize_separators(char *path)
 {
-	if (!path)
-		return NULL;
-
-	char *copy = strdup(path);
-	if (NULL == copy)
-		return NULL;
-	char *ptr = copy;
+#ifdef _WIN32
+	for (; *path != '\0'; path++)
+		if (*path == PKG_DIR_SEP_S)
+			*path = '/';
+#else
+	(void) path;
+#endif
+}
 
-	for (int ii = 0; copy[ii]; ii++)
+/*
+ * !doc
+ *
+ * .. c:function:: bool pkgconf_path_relocate(pkgconf_buffer_t *buf)
+ *
+ *    Normalizes a path in place: folds the platform separator to '/' and
+ *    collapses runs of '/'.
+ *
+ *    :param pkgconf_buffer_t* buf: The path to relocate.
+ *    :return: true on success, false on error
+ *    :rtype: bool
+ */
+bool
+pkgconf_path_relocate(pkgconf_buffer_t *buf)
+{
+	char *base = buf->base;
+	char *w;
+	const char *r;
+
+	if (base == NULL || pkgconf_buffer_len(buf) == 0)
+		return true;
+
+	/* fold the platform separator to '/' before collapsing runs, so that a
+	 * mix of '\' and '/' (as seen on Windows) is treated uniformly. */
+	pkgconf_path_normalize_separators(base);
+
+	/* collapse runs of '/' in place; the result never grows, so w <= r always */
+	w = base;
+	for (r = base; *r != '\0'; r++)
 	{
-		*ptr++ = path[ii];
-		if ('/' == path[ii])
-		{
-			ii++;
-			while ('/' == path[ii])
-				ii++;
-			ii--;
-		}
+		*w++ = *r;
+		if (*r == '/')
+			while (r[1] == '/')
+				r++;
 	}
-	*ptr = '\0';
 
-	return copy;
+	*w = '\0';
+	buf->end = w;
+
+	return true;
 }
 
 /*
  * !doc
  *
- * .. c:function:: bool pkgconf_path_relocate(char *buf, size_t buflen)
+ * .. c:function:: bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf)
  *
- *    Relocates a path, possibly calling normpath() on it.
+ *    Trims the basename from a path.
  *
- *    :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
+ *    :param pkgconf_buffer_t* buf: The path to trim.
+ *    :return: true if a separator was found and the path was trimmed, false otherwise
  *    :rtype: bool
  */
 bool
-pkgconf_path_relocate(char *buf, size_t buflen)
+pkgconf_path_trim_basename(pkgconf_buffer_t *buf)
+{
+	char *sep;
+
+	if (!pkgconf_buffer_len(buf))
+		return false;
+
+	sep = strrchr(buf->base, PKG_DIR_SEP_S);
+#ifdef _WIN32
+	char *sep2 = strrchr(buf->base, '/');
+	if (sep2 != NULL && (sep == NULL || sep2 > sep))
+		sep = sep2;
+#endif
+
+	if (sep != NULL)
+	{
+		*sep = '\0';
+		buf->end = sep;
+		return true;
+	}
+
+	return false;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *pkgconf_path_find_basename(const char *path)
+ *
+ *    Finds the basename from a path.
+ *
+ *    :param char* path: The path to find the basename from.
+ *    :return: a pointer to the basename
+ *    :rtype: const char *
+ */
+const char *
+pkgconf_path_find_basename(const char *path)
 {
-	char *tmpbuf;
+	const char *sep;
 
-	if ((tmpbuf = normpath(buf)) != NULL)
+	sep = strrchr(path, PKG_DIR_SEP_S);
+#ifdef _WIN32
+	const char *sep2 = strrchr(path, '/');
+	if (sep2 != NULL && (sep == NULL || sep2 > sep))
+		sep = sep2;
+#endif
+
+	if (sep != NULL)
+		return sep + 1;
+
+	return path;
+}
+
+#ifdef _WIN32
+#define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
+/*
+ * !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.
+ *
+ *    .. warning::
+ *       The Windows registry search path mechanism is deprecated and will be
+ *       removed in pkgconf 3.1.  Use ``PKG_CONFIG_PATH`` or configure search
+ *       paths explicitly instead.  Avoid using this function directly in new
+ *       code.
+ *
+ *    :param pkgconf_client_t* client: pkgconf client
+ *    :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(pkgconf_client_t *client, 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;
+
+	pkgconf_warn(client,
+		"WARNING: support for reading PKG_CONFIG_PATH from the Windows registry "
+		"is deprecated and will be removed in pkgconf 3.1\n");
+
+	while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL)
+			== ERROR_SUCCESS)
 	{
-		size_t tmpbuflen = strlen(tmpbuf);
-		if (tmpbuflen > buflen)
+		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)
 		{
-			free(tmpbuf);
-			return false;
+			pkgconf_path_add(pathbuf, dir_list, filter);
+			added++;
 		}
 
-		pkgconf_strlcpy(buf, tmpbuf, buflen);
-		free(tmpbuf);
+		bufsize = sizeof buf;
 	}
 
-	return true;
+	RegCloseKey(key);
+	return added;
+}
+#endif
+
+bool
+pkgconf_path_is_plausible(const pkgconf_buffer_t *buf)
+{
+	const char *s;
+
+	if (buf == NULL)
+		return false;
+
+	s = pkgconf_buffer_str(buf);
+	if (s == NULL)
+		return false;
+
+	/* skip leading whitespace */
+	while (*s != '\0' && isspace((unsigned char)*s))
+		s++;
+
+	if (*s == '\0')
+		return false;
+
+	/* POSIX absolute path */
+	if (*s == '/')
+		return true;
+
+	/* ./ or ../ relative path */
+	if (s[0] == '.' && (s[1] == '/' || s[1] == '\\'))
+		return true;
+
+	if (s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\\'))
+		return true;
+
+	/* Windows drive path: C:/... or C:\... */
+	if (isalpha((unsigned char)s[0]) && s[1] == ':' && (s[2] == '/' || s[2] == '\\'))
+		return true;
+
+	/* anything with a path separator seems plausible, for example "Program Files/MySDK" */
+	for (const char *p = s; *p != '\0'; p++)
+	{
+		if (*p == '/' || *p == '\\')
+			return true;
+	}
+
+	return false;
 }
Index: libpkgconf/path.h
===================================================================
RCS file: libpkgconf/path.h
diff -N libpkgconf/path.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/path.h	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,9 @@
+#ifndef LIBPKGCONF_PATH_H
+#define LIBPKGCONF_PATH_H
+
+#include <libpkgconf/libpkgconf.h>
+
+PKGCONF_API bool pkgconf_path_trim_basename(pkgconf_buffer_t *buf);
+PKGCONF_API const char *pkgconf_path_find_basename(const char *path);
+
+#endif
Index: libpkgconf/personality.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/personality.c,v
diff -u -p -r1.1.1.1 personality.c
--- libpkgconf/personality.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/personality.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * personality.c
  * libpkgconf cross-compile personality database
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2018 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -24,10 +26,6 @@
  * =========================
  */
 
-#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
@@ -37,15 +35,6 @@ static unsigned default_personality_init
 
 static pkgconf_cross_personality_t default_personality = {
 	.name = "default",
-#ifdef _WIN32
-	/* PE/COFF uses a different linking model than ELF and Mach-O, where
-	 * all transitive dependency references must be processed by the linker
-	 * when linking the final executable image, even if those dependencies
-	 * are pulled in as DLLs.
-	 * This translates to always using --static on Windows targets.
-	 */
-	.want_default_static = true,
-#endif
 };
 
 static inline void
@@ -53,29 +42,36 @@ build_default_search_path(pkgconf_list_t
 {
 #ifdef _WIN32
 	char namebuf[MAX_PATH];
-	char outbuf[MAX_PATH];
+	pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
 	char *p;
 
-	int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf);
-	char * winslash;
+	/* Reserve one byte for the NUL: GetModuleFileName returns the size passed
+	 * to it (nSize) when the path is truncated, so passing sizeof namebuf could
+	 * yield sizepath == sizeof namebuf and overflow namebuf[] by one byte below.
+	 *
+	 * See the GetModuleFileNameA return-value documentation:
+	 * https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea
+	 */
+	int sizepath = GetModuleFileName(NULL, namebuf, sizeof namebuf - 1);
 	namebuf[sizepath] = '\0';
-	while ((winslash = strchr (namebuf, '\\')) != NULL)
-	{
-		*winslash = '/';
-	}
+
+	pkgconf_path_normalize_separators(namebuf);
+
 	p = strrchr(namebuf, '/');
 	if (p == NULL)
+	{
 		pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true);
-
+		return;
+	}
 	*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);
+
+	pkgconf_buffer_append_fmt(&pathbuf, "%s/../lib/pkgconfig", namebuf);
+	pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true);
+	pkgconf_buffer_rewind(&pathbuf);
+
+	pkgconf_buffer_append_fmt(&pathbuf, "%s/../share/pkgconfig", namebuf);
+	pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, true);
+	pkgconf_buffer_finalize(&pathbuf);
 #elif __HAIKU__
 	char **paths;
 	size_t count;
@@ -92,7 +88,7 @@ build_default_search_path(pkgconf_list_t
 		paths = NULL;
 	}
 #else
-	pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, true);
+	pkgconf_path_split(PKG_DEFAULT_PATH, dirlist, false);
 #endif
 }
 
@@ -130,7 +126,8 @@ pkgconf_cross_personality_default(void)
  *
  * .. c:function:: void pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *)
  *
- *    Decrements the count of default cross personality instances.
+ *    Destroys a cross personality object and/or decreases the reference count on the
+ *    default cross personality object.
  *
  *    Not thread safe.
  *
@@ -139,11 +136,28 @@ pkgconf_cross_personality_default(void)
 void
 pkgconf_cross_personality_deinit(pkgconf_cross_personality_t *personality)
 {
-    if (--default_personality_init == 0) {
-        pkgconf_path_free(&personality->dir_list);
-        pkgconf_path_free(&personality->filter_libdirs);
-        pkgconf_path_free(&personality->filter_includedirs);
-    }
+	/* 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
@@ -159,7 +173,7 @@ valid_triplet(const char *triplet)
 	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 void (*personality_keyword_func_t)(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value);
 typedef struct {
 	const char *keyword;
 	const personality_keyword_func_t func;
@@ -167,30 +181,34 @@ typedef struct {
 } 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)
+personality_bool_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
 	(void) keyword;
-	(void) lineno;
+	(void) warnprefix;
 
 	bool *dest = (bool *)((char *) p + offset);
-	*dest = strcasecmp(value, "true") || strcasecmp(value, "yes") || *value == '1';
+	*dest = strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0;
 }
 
 static void
-personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const size_t lineno, const ptrdiff_t offset, char *value)
+personality_copy_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
 	(void) keyword;
-	(void) lineno;
+	(void) warnprefix;
 
 	char **dest = (char **)((char *) p + offset);
+
+	if (*dest != NULL)
+		free(*dest);
+
 	*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)
+personality_fragment_func(pkgconf_cross_personality_t *p, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
 	(void) keyword;
-	(void) lineno;
+	(void) warnprefix;
 
 	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) p + offset);
 	pkgconf_path_split(value, dest, false);
@@ -215,8 +233,9 @@ personality_keyword_pair_cmp(const void 
 }
 
 static void
-personality_keyword_set(pkgconf_cross_personality_t *p, const size_t lineno, const char *keyword, char *value)
+personality_keyword_set(void *data, const char *warnprefix, const char *keyword, const char *value)
 {
+	pkgconf_cross_personality_t *p = data;
 	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);
@@ -224,7 +243,7 @@ personality_keyword_set(pkgconf_cross_pe
 	if (pair == NULL || pair->func == NULL)
 		return;
 
-	pair->func(p, keyword, lineno, pair->offset, value);
+	pair->func(p, keyword, warnprefix, pair->offset, value);
 }
 
 static const pkgconf_parser_operand_func_t personality_parser_ops[256] = {
@@ -241,33 +260,54 @@ personality_warn_func(void *p, const cha
 	(void) p;
 
 	va_start(va, fmt);
-	vfprintf(stderr, fmt, va);
+	pkgconf_output_file_vfmt(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];
+	pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
 	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);
+	{
+		if (!pkgconf_buffer_append(&pathbuf, path))
+			return NULL;
+	}
 	else if (datadir)
-		snprintf(pathbuf, sizeof pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet);
+	{
+		if (!pkgconf_buffer_append_fmt(&pathbuf, "%s/pkgconfig/personality.d/%s.personality", path, triplet))
+			return NULL;
+	}
 	else
-		snprintf(pathbuf, sizeof pathbuf, "%s/%s.personality", path, triplet);
+	{
+		if (!pkgconf_buffer_append_fmt(&pathbuf, "%s/%s.personality", path, triplet))
+			return NULL;
+	}
 
-	f = fopen(pathbuf, "r");
-	if (f == NULL)
+	p = calloc(1, sizeof(pkgconf_cross_personality_t));
+	if (p == NULL)
+	{
+		pkgconf_buffer_finalize(&pathbuf);
 		return NULL;
+	}
 
-	p = calloc(1, sizeof(pkgconf_cross_personality_t));
 	if (triplet != NULL)
 		p->name = strdup(triplet);
-	pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pathbuf);
+
+	f = fopen(pkgconf_buffer_str(&pathbuf), "rb");
+	if (f == NULL)
+	{
+		pkgconf_buffer_finalize(&pathbuf);
+		pkgconf_cross_personality_deinit(p);
+		return NULL;
+	}
+
+	pkgconf_parser_parse(f, p, personality_parser_ops, personality_warn_func, pkgconf_buffer_str(&pathbuf));
+	pkgconf_buffer_finalize(&pathbuf);
 
 	return p;
 }
@@ -289,7 +329,6 @@ pkgconf_cross_personality_find(const cha
 	pkgconf_node_t *n;
 	pkgconf_cross_personality_t *out = NULL;
 #if ! defined(_WIN32) && ! defined(__HAIKU__)
-	char pathbuf[PKGCONF_ITEM_SIZE];
 	const char *envvar;
 #endif
 
@@ -304,16 +343,20 @@ pkgconf_cross_personality_find(const cha
 	envvar = getenv("XDG_DATA_HOME");
 	if (envvar != NULL)
 		pkgconf_path_add(envvar, &plist, true);
-	else {
+	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);
+		if (envvar != NULL)
+		{
+			pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
+
+			pkgconf_buffer_append_fmt(&pathbuf, "%s/.local/share", envvar);
+			pkgconf_path_add(pkgconf_buffer_str(&pathbuf), &plist, true);
+			pkgconf_buffer_finalize(&pathbuf);
 		}
 	}
 
-	pkgconf_path_build_from_environ("XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true);
+	pkgconf_path_build_from_environ(NULL, "XDG_DATA_DIRS", "/usr/local/share" PKG_CONFIG_PATH_SEP_S "/usr/share", &plist, true);
 
 	PKGCONF_FOREACH_LIST_ENTRY(plist.head, n)
 	{
Index: libpkgconf/pkg.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/pkg.c,v
diff -u -p -r1.1.1.1 pkg.c
--- libpkgconf/pkg.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/pkg.c	4 Aug 2026 17:05:31 -0000
@@ -2,6 +2,8 @@
  * pkg.c
  * higher-level dependency graph compilation, management and manipulation
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2011, 2012, 2013 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -16,15 +18,7 @@
 #include <libpkgconf/config.h>
 #include <libpkgconf/stdinc.h>
 #include <libpkgconf/libpkgconf.h>
-
-#ifndef _WIN32
-#include <fcntl.h>    // open
-#include <libgen.h>   // basename/dirname
-#include <sys/stat.h> // lstat, S_ISLNK
-#include <unistd.h>   // close, readlinkat
-
-#include <string.h>
-#endif
+#include <libpkgconf/path.h>
 
 /*
  * !doc
@@ -36,14 +30,6 @@
  * routines.
  */
 
-#ifdef _WIN32
-#	define PKG_CONFIG_REG_KEY "Software\\pkgconfig\\PKG_CONFIG_PATH"
-#	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
@@ -52,11 +38,15 @@ pkgconf_pkg_traverse_main(pkgconf_client
 	pkgconf_pkg_traverse_func_t func,
 	void *data,
 	int maxdepth,
-	unsigned int skip_flags);
+	unsigned int skip_flags,
+	unsigned int iter_flags);
 
 static inline bool
 str_has_suffix(const char *str, const char *suffix)
 {
+	if (str == NULL || suffix == NULL)
+		return false;
+
 	size_t str_len = strlen(str);
 	size_t suf_len = strlen(suffix);
 
@@ -69,48 +59,58 @@ str_has_suffix(const char *str, const ch
 static char *
 pkg_get_parent_dir(pkgconf_pkg_t *pkg)
 {
-	char buf[PKGCONF_ITEM_SIZE], *pathbuf;
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (!pkgconf_buffer_append(&buf, pkg->filename))
+		goto fail;
 
-	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))
+
+	while (buf.base != NULL &&
+		!lstat(buf.base, &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 <source> <target>
-		 *
-		 * 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);
+		char sourcebuf[PKGCONF_ITEM_SIZE];
+		char *targetfilename, *targetdir;
+
+		pkgconf_buffer_rewind(&pathbuf);
+		if (!pkgconf_buffer_append(&pathbuf, buf.base))
+			goto fail;
 
+		targetfilename = strrchr(pathbuf.base, '/');
+		if (targetfilename != NULL)
+		{
+			*targetfilename++ = '\0';
+			targetdir = pathbuf.base;
+
+			if (*targetdir == '\0')
+				targetdir = "/";
+		}
+		else
+		{
+			targetfilename = pathbuf.base;
+			targetdir = ".";
+		}
+
+#if HAVE_DECL_READLINKAT
 		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);
+#else
+		ssize_t len = readlink(buf.base, sourcebuf, sizeof(sourcebuf) - 1);
+#endif
 
 		if (len == -1)
 			break;
 		sourcebuf[len] = '\0';
 
-		memset(buf, '\0', sizeof buf);
+		pkgconf_buffer_rewind(&buf);
+
 		/*
 		 * The logic here can be a bit tricky, so here's a table:
 		 *
@@ -121,26 +121,31 @@ pkg_get_parent_dir(pkgconf_pkg_t *pkg)
 		 *     /bar (absolute)  |  /foo/link (absolute)  |         /bar (absolute)
 		 *   ../bar (relative)  |  /foo/link (absolute)  |  /foo/../bar (relative)
 		 */
-		if ((sourcebuf[0] != '/')        /* absolute path in <source> wins */
-		    && (strcmp(targetdir, "."))) /* do not prepend "." */
+		if ((sourcebuf[0] != '/') && strcmp(targetdir, "."))
 		{
-			pkgconf_strlcat(buf, targetdir, sizeof buf);
-			pkgconf_strlcat(buf, "/", sizeof buf);
+			if (!pkgconf_buffer_append(&buf, targetdir) || !pkgconf_buffer_push_byte(&buf, '/'))
+				goto fail;
 		}
-		pkgconf_strlcat(buf, sourcebuf, sizeof buf);
+
+		if (!pkgconf_buffer_append(&buf, sourcebuf))
+			goto fail;
 	}
 #endif
 
-	pathbuf = strrchr(buf, PKG_DIR_SEP_S);
-	if (pathbuf == NULL)
-		pathbuf = strrchr(buf, '/');
-	if (pathbuf != NULL)
-		pathbuf[0] = '\0';
+	pkgconf_buffer_finalize(&pathbuf);
+
+	if (pkgconf_buffer_len(&buf) > 0)
+		pkgconf_path_trim_basename(&buf);
 
-	return strdup(buf);
+	return pkgconf_buffer_freeze(&buf);
+
+fail:
+	pkgconf_buffer_finalize(&pathbuf);
+	pkgconf_buffer_finalize(&buf);
+	return NULL;
 }
 
-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 void (*pkgconf_pkg_parser_keyword_func_t)(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value);
 typedef struct {
 	const char *keyword;
 	const pkgconf_pkg_parser_keyword_func_t func;
@@ -154,26 +159,85 @@ static int pkgconf_pkg_parser_keyword_pa
 }
 
 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)
+pkgconf_pkg_parser_tuple_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
 	(void) keyword;
-	(void) lineno;
+	(void) warnprefix;
 
 	char **dest = (char **)((char *) pkg + offset);
-	*dest = pkgconf_tuple_parse(client, &pkg->vars, value, pkg->flags);
+
+	if (*dest != NULL)
+		free(*dest);
+
+	*dest = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL);
 }
 
 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)
+pkgconf_pkg_parser_bufferset_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+	(void) keyword;
+	(void) warnprefix;
+
+	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+
+	if (pkgconf_bytecode_eval_str_to_buf(client, &pkg->vars, value, NULL, &buf))
+		pkgconf_bufferset_extend(dest, &buf);
+
+	pkgconf_buffer_finalize(&buf);
+}
+
+/* parses a comma-separated list of ABI tags, lowercasing each, into a bufferset */
+static void
+pkgconf_pkg_parser_link_abi_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+	(void) keyword;
+	(void) warnprefix;
+
+	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+	char *expanded = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL);
+
+	if (expanded == NULL)
+		return;
+
+	for (char *p = expanded; *p != '\0';)
+	{
+		pkgconf_buffer_t tag = PKGCONF_BUFFER_INITIALIZER;
+
+		while (*p == ',' || isspace((unsigned char) *p))
+			p++;
+
+		while (*p != '\0' && *p != ',' && !isspace((unsigned char) *p))
+		{
+			if (!pkgconf_buffer_push_byte(&tag, (char) tolower((unsigned char) *p++)))
+			{
+				pkgconf_buffer_finalize(&tag);
+				free(expanded);
+				return;
+			}
+		}
+
+		if (pkgconf_buffer_len(&tag))
+			pkgconf_bufferset_extend(dest, &tag);
+
+		pkgconf_buffer_finalize(&tag);
+	}
+
+	free(expanded);
+}
+
+static void
+pkgconf_pkg_parser_version_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, 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);
+	p = pkgconf_bytecode_eval_str(client, &pkg->vars, value, NULL);
+	if (p == NULL)
+		return;
 
 	len = strcspn(p, " \t");
 	if (len != strlen(p))
@@ -181,88 +245,127 @@ pkgconf_pkg_parser_version_func(pkgconf_
 		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);
+		pkgconf_warn(client, "%s: warning: malformed version field with whitespace, trimming to [%s]\n",
+			warnprefix, p);
 	}
 
+	if (*dest != NULL)
+		free(*dest);
+
 	*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_pkg_parser_fragment_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, 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);
+		pkgconf_warn(client, "%s: warning: unable to parse field '%s' into an argument vector, value [%s]\n",
+			warnprefix, 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)
+pkgconf_pkg_parser_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
-	(void) keyword;
-	(void) lineno;
-
 	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+
+	if (dest->tail != NULL)
+	{
+		pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n",
+			warnprefix, keyword);
+	}
+
 	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)
+pkgconf_pkg_parser_internal_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
-	(void) keyword;
-	(void) lineno;
-
 	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+
+	if (dest->tail != NULL)
+	{
+		pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n",
+			warnprefix, keyword);
+	}
+
 	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)
+pkgconf_pkg_parser_private_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
 {
-	(void) keyword;
-	(void) lineno;
-
 	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+
+	if (dest->tail != NULL)
+	{
+		pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n",
+			warnprefix, keyword);
+	}
+
 	pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_PRIVATE);
 }
 
+/* a variant of pkgconf_pkg_parser_dependency_func which colors the dependency node as a "shared" dependency. */
+static void
+pkgconf_pkg_parser_shared_dependency_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+
+	if (dest->tail != NULL)
+	{
+		pkgconf_warn(client, "%s: warning: merging duplicate field '%s' (undefined behavior)\n",
+			warnprefix, keyword);
+	}
+
+	pkgconf_dependency_parse(client, pkg, dest, value, PKGCONF_PKG_DEPF_SHARED);
+}
+
+/* Evaluates SPDX expression or parses comma separated list of licenses */
+static void
+pkgconf_pkg_evaluate_license_func(pkgconf_client_t *client, pkgconf_pkg_t *pkg, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value)
+{
+	pkgconf_list_t *dest = (pkgconf_list_t *)((char *) pkg + offset);
+
+	if (!pkgconf_license_evaluate(client, pkg, dest, value, 0))
+		pkgconf_warn(client, "%s: warning: license field '%s' could not be fully evaluated\n",
+			warnprefix, keyword);
+}
+
 /* 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)},
+	{"CFLAGS.shared", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, cflags_shared)},
 	{"Conflicts", pkgconf_pkg_parser_dependency_func, offsetof(pkgconf_pkg_t, conflicts)},
-	{"Copyright", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, copyright)},
+	{"Copyright", pkgconf_pkg_parser_bufferset_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)},
+	{"LIBS.shared", pkgconf_pkg_parser_fragment_func, offsetof(pkgconf_pkg_t, libs_shared)},
+	{"License", pkgconf_pkg_evaluate_license_func, offsetof(pkgconf_pkg_t, license)},
+	{"License.file", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, license_file)},
+	{"Link.ABI", pkgconf_pkg_parser_link_abi_func, offsetof(pkgconf_pkg_t, link_abi)},
 	{"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)},
+	{"Requires.shared", pkgconf_pkg_parser_shared_dependency_func, offsetof(pkgconf_pkg_t, requires_shared)},
+	{"Source", pkgconf_pkg_parser_tuple_func, offsetof(pkgconf_pkg_t, source)},
 	{"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_parser_keyword_set(void *opaque, const char *warnprefix, const char *keyword, const char *value)
 {
 	pkgconf_pkg_t *pkg = opaque;
 
@@ -273,44 +376,29 @@ pkgconf_pkg_parser_keyword_set(void *opa
 	if (pair == NULL || pair->func == NULL)
 		return;
 
-	pair->func(pkg->owner, pkg, keyword, lineno, pair->offset, value);
+	pair->func(pkg->owner, pkg, keyword, warnprefix, pair->offset, value);
 }
 
-static const char *
-determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen)
+static bool
+determine_prefix(const pkgconf_pkg_t *pkg, pkgconf_buffer_t *pathbuf)
 {
-	char *pathiter;
-
-	pkgconf_strlcpy(buf, pkg->filename, buflen);
-	pkgconf_path_relocate(buf, buflen);
+	if (!pkgconf_buffer_append(pathbuf, pkg->filename))
+		return false;
 
-	pathiter = strrchr(buf, PKG_DIR_SEP_S);
-	if (pathiter == NULL)
-		pathiter = strrchr(buf, '/');
-	if (pathiter != NULL)
-		pathiter[0] = '\0';
+	pkgconf_path_relocate(pathbuf);
 
-	pathiter = strrchr(buf, PKG_DIR_SEP_S);
-	if (pathiter == NULL)
-		pathiter = strrchr(buf, '/');
-	if (pathiter == NULL)
-		return NULL;
+	pkgconf_path_trim_basename(pathbuf);
 
-	/* parent dir is not pkgconfig, can't relocate then */
-	if (strcmp(pathiter + 1, "pkgconfig"))
-		return NULL;
+	if (strcmp(pkgconf_path_find_basename(pkgconf_buffer_str_or_empty(pathbuf)), "pkgconfig"))
+		return false;
 
-	/* 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;
+	if (!pkgconf_path_trim_basename(pathbuf))
+		return false;
 
-	pathiter[0] = '\0';
+	if (!pkgconf_path_trim_basename(pathbuf))
+		return false;
 
-	return buf;
+	return true;
 }
 
 /*
@@ -326,22 +414,33 @@ determine_prefix(const pkgconf_pkg_t *pk
 static char *
 convert_path_to_value(const char *path)
 {
-	char *buf = calloc(1, (strlen(path) + 1) * 2);
-	char *bptr = buf;
+	pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
 	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;
+		char c = *i;
+
+#ifdef _WIN32
+		if (c == PKG_DIR_SEP_S)
+			c = '/';
+#endif
+
+		if (c == ' ')
+		{
+			if (!pkgconf_buffer_push_byte(&buf, '\\') ||
+				!pkgconf_buffer_push_byte(&buf, ' '))
+				goto fail;
+		}
+		else if (!pkgconf_buffer_push_byte(&buf, c))
+			goto fail;
 	}
 
-	return buf;
+	return pkgconf_buffer_freeze(&buf);
+
+fail:
+	pkgconf_buffer_finalize(&buf);
+	return NULL;
 }
 
 static void
@@ -381,47 +480,142 @@ is_path_prefix_equal(const char *path1, 
 #endif
 }
 
+static inline bool
+is_path_separator(char c)
+{
+#ifdef _WIN32
+	return c == '/' || c == '\\';
+#else
+	return c == '/';
+#endif
+}
+
+static inline const char *
+lookup_val_from_env(const pkgconf_client_t *client, const char *pkg_id, const char *keyword)
+{
+	pkgconf_buffer_t env_var = PKGCONF_BUFFER_INITIALIZER;
+	const char *result;
+	char *c;
+
+	if (!pkgconf_buffer_join(&env_var, '_', "PKG_CONFIG", pkg_id, keyword, NULL))
+	{
+		pkgconf_buffer_finalize(&env_var);
+		return NULL;
+	}
+
+	for (c = env_var.base; *c != '\0'; c++)
+	{
+		*c = (char) toupper((unsigned char) *c);
+
+		if (!isalnum((unsigned char) *c))
+			*c = '_';
+	}
+
+	result = pkgconf_client_getenv(client, pkgconf_buffer_str(&env_var));
+
+	pkgconf_buffer_finalize(&env_var);
+
+	return result;
+}
+
 static void
-pkgconf_pkg_parser_value_set(void *opaque, const size_t lineno, const char *keyword, const char *value)
+pkgconf_pkg_parser_value_set(void *opaque, const char *warnprefix, const char *keyword, const char *value)
 {
-	char canonicalized_value[PKGCONF_ITEM_SIZE];
+	pkgconf_buffer_t canonicalized_value = PKGCONF_BUFFER_INITIALIZER;
 	pkgconf_pkg_t *pkg = opaque;
+	const char *env_content;
 
-	(void) lineno;
+	(void) warnprefix;
 
-	pkgconf_strlcpy(canonicalized_value, value, sizeof canonicalized_value);
-	canonicalize_path(canonicalized_value);
+	env_content = lookup_val_from_env(pkg->owner, pkg->id, keyword);
+	if (env_content != NULL)
+	{
+		PKGCONF_TRACE(pkg->owner, "overriding %s from environment", keyword);
+		value = env_content;
+	}
+
+	if (!(pkg->owner->flags & PKGCONF_PKG_PKGF_REDEFINE_PREFIX))
+	{
+		pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true, pkg->flags);
+		return;
+	}
+
+	if (!pkgconf_buffer_append(&canonicalized_value, value))
+		goto out;
+
+	canonicalize_path(canonicalized_value.base);
 
 	/* 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)))
+	if (strcmp(keyword, pkg->owner->prefix_varname))
 	{
-		char newvalue[PKGCONF_ITEM_SIZE];
+		if (pkgconf_buffer_len(&pkg->orig_prefix) != 0)
+		{
+			const char *op = pkgconf_buffer_str_or_empty(&pkg->orig_prefix);
+			const char *cv = pkgconf_buffer_str(&canonicalized_value);
+			size_t oplen = pkgconf_buffer_len(&pkg->orig_prefix);
+
+			/* Match orig_prefix as a directory path: disregard any trailing
+			 * separator so oplen describes the directory itself, and require
+			 * the value to continue on a component boundary.  This leaves the
+			 * separator at cv[oplen] as the tail's leading character, which
+			 * joins it to calculated_prefix (which never has a trailing one).
+			 */
+			while (oplen > 1 && is_path_separator(op[oplen - 1]))
+				oplen--;
+
+			if (is_path_prefix_equal(cv, op, oplen) &&
+				(cv[oplen] == '\0' || is_path_separator(cv[oplen])))
+			{
+				pkgconf_buffer_t newvalue = PKGCONF_BUFFER_INITIALIZER;
+
+				if (!pkgconf_buffer_append(&newvalue, pkgconf_buffer_str_or_empty(&pkg->calculated_prefix)) ||
+					!pkgconf_buffer_append(&newvalue, cv + oplen))
+				{
+					pkgconf_buffer_finalize(&newvalue);
+					goto out;
+				}
+
+				pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, pkgconf_buffer_str(&newvalue), false, pkg->flags);
+				pkgconf_buffer_finalize(&newvalue);
+
+				goto out;
+			}
+		}
 
-		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);
+		pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
 
-		if (relvalue != NULL)
+		if (determine_prefix(pkg, &pathbuf))
 		{
+			const char *relvalue = pkgconf_buffer_str(&pathbuf);
 			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);
+
+			if (prefix_value == NULL ||
+				!pkgconf_buffer_append(&pkg->orig_prefix, pkgconf_buffer_str(&canonicalized_value)) ||
+				!pkgconf_buffer_append(&pkg->calculated_prefix, prefix_value))
+			{
+				free(prefix_value);
+				pkgconf_buffer_finalize(&pathbuf);
+				goto out;
+			}
+
+			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);
+
+		pkgconf_buffer_finalize(&pathbuf);
 	}
+
+out:
+	pkgconf_buffer_finalize(&canonicalized_value);
 }
 
 typedef struct {
@@ -440,11 +634,12 @@ static const pkgconf_parser_operand_func
 	['='] = 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(void *pkg_p, const char *fmt, ...) PRINTFLIKE(2, 3);
 
 static void
-pkg_warn_func(pkgconf_pkg_t *pkg, const char *fmt, ...)
+pkg_warn_func(void *pkg_p, const char *fmt, ...)
 {
+	pkgconf_pkg_t *pkg = pkg_p;
 	char buf[PKGCONF_ITEM_SIZE];
 	va_list va;
 
@@ -475,10 +670,78 @@ pkgconf_pkg_validate(const pkgconf_clien
 	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_file != NULL)
+		free(pkg->license_file);
+
+	if (pkg->maintainer != NULL)
+		free(pkg->maintainer);
+
+	if (pkg->why != NULL)
+		free(pkg->why);
+
+	if (pkg->source != NULL)
+		free(pkg->source);
+
+	pkgconf_buffer_finalize(&pkg->orig_prefix);
+	pkgconf_buffer_finalize(&pkg->calculated_prefix);
+
+	free(pkg);
+}
+
+static void
+pkg_free_lists(pkgconf_pkg_t *pkg)
+{
+	pkgconf_bufferset_free(&pkg->copyright);
+	pkgconf_bufferset_free(&pkg->link_abi);
+
+	pkgconf_dependency_free(&pkg->required);
+	pkgconf_dependency_free(&pkg->requires_private);
+	pkgconf_dependency_free(&pkg->requires_shared);
+	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->cflags_shared);
+	pkgconf_license_free(&pkg->license);
+	pkgconf_fragment_free(&pkg->libs);
+	pkgconf_fragment_free(&pkg->libs_private);
+	pkgconf_fragment_free(&pkg->libs_shared);
+
+	pkgconf_tuple_free(&pkg->vars);
+}
+
 /*
  * !doc
  *
- * .. c:function:: pkgconf_pkg_t *pkgconf_pkg_new_from_file(const pkgconf_client_t *client, const char *filename, FILE *f, unsigned int flags)
+ * .. 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.
  *
@@ -490,16 +753,49 @@ pkgconf_pkg_validate(const pkgconf_clien
  *    :rtype: pkgconf_pkg_t *
  */
 pkgconf_pkg_t *
-pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *filename, FILE *f, unsigned int flags)
+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, "rb");
+	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);
-	pkg->flags = flags;
+	if (pkg->pc_filedir == NULL)
+	{
+		fclose(f);
+		pkg_free_object(pkg);
+		return NULL;
+	}
+
+	/* keep pc_filedir in canonical (forward-slash) form so it compares
+	 * consistently against sysroot_dir and the search-path entries below */
+	pkgconf_path_normalize_separators(pkg->pc_filedir);
 
 	char *pc_filedir_value = convert_path_to_value(pkg->pc_filedir);
 	pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pc_filedir_value, true, pkg->flags);
@@ -509,26 +805,20 @@ pkgconf_pkg_new_from_file(pkgconf_client
 	 * package.
 	 * See https://github.com/pkgconf/pkgconf/issues/213
 	 */
-	if (client->sysroot_dir && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir)))
+	if (client->sysroot_dir != NULL && strncmp(pkg->pc_filedir, client->sysroot_dir, strlen(client->sysroot_dir)) &&
+		!(client->flags & PKGCONF_PKG_PKGF_PKGCONF1_SYSROOT_RULES))
 		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(pkgconf_path_find_basename(pkg->filename));
+	if (pkg->id == NULL)
+	{
+		fclose(f);
+		pkg_free_lists(pkg);
+		pkg_free_object(pkg);
+		return NULL;
+	}
 
-	pkg->id = strdup(idptr);
 	idptr = strrchr(pkg->id, '.');
 	if (idptr)
 		*idptr = '\0';
@@ -540,7 +830,8 @@ pkgconf_pkg_new_from_file(pkgconf_client
 			*idptr = '\0';
 	}
 
-	pkgconf_parser_parse(f, pkg, pkg_parser_funcs, (pkgconf_parser_warn_func_t) pkg_warn_func, pkg->filename);
+	pkgconf_parser_parse(f, pkg, pkg_parser_funcs, pkg_warn_func, pkg->filename);
+	fclose(f);
 
 	if (!pkgconf_pkg_validate(client, pkg))
 	{
@@ -549,7 +840,30 @@ pkgconf_pkg_new_from_file(pkgconf_client
 		return NULL;
 	}
 
+	/* a package that does not declare a Link.ABI defaults to the C ABI; a
+	 * declared Link.ABI replaces this default rather than adding to it.
+	 */
+	if (pkg->link_abi.head == NULL)
+	{
+		pkgconf_buffer_t abibuf = PKGCONF_BUFFER_INITIALIZER;
+
+		if (!pkgconf_buffer_append(&abibuf, "c"))
+		{
+			pkgconf_pkg_free(client, pkg);
+			return NULL;
+		}
+
+		pkgconf_bufferset_extend(&pkg->link_abi, &abibuf);
+		pkgconf_buffer_finalize(&abibuf);
+	}
+
 	pkgconf_dependency_t *dep = pkgconf_dependency_add(client, &pkg->provides, pkg->id, pkg->version, PKGCONF_CMP_EQUAL, 0);
+	if (dep == NULL)
+	{
+		pkgconf_pkg_free(client, pkg);
+		return NULL;
+	}
+
 	pkgconf_dependency_unref(dep->owner, dep);
 
 	return pkgconf_pkg_ref(client, pkg);
@@ -577,55 +891,12 @@ pkgconf_pkg_free(pkgconf_client_t *clien
 
 	pkgconf_cache_remove(client, 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);
+	pkg_free_lists(pkg);
 
 	if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
 		return;
 
-	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);
+	pkg_free_object(pkg);
 }
 
 /*
@@ -666,6 +937,11 @@ pkgconf_pkg_ref(pkgconf_client_t *client
 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);
 
@@ -680,26 +956,36 @@ 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;
-	FILE *f;
-	char locbuf[PKGCONF_ITEM_SIZE];
-	char uninst_locbuf[PKGCONF_ITEM_SIZE];
+	pkgconf_buffer_t locbuf = PKGCONF_BUFFER_INITIALIZER;
 
 	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) && (f = fopen(uninst_locbuf, "r")) != NULL)
+	if (!(client->flags & PKGCONF_PKG_PKGF_NO_UNINSTALLED))
 	{
-		PKGCONF_TRACE(client, "found (uninstalled): %s", uninst_locbuf);
-		pkg = pkgconf_pkg_new_from_file(client, uninst_locbuf, f, PKGCONF_PKG_PROPF_UNINSTALLED);
+		pkgconf_buffer_append(&locbuf, path);
+		pkgconf_buffer_push_byte(&locbuf, PKG_DIR_SEP_S);
+		pkgconf_buffer_append(&locbuf, name);
+		pkgconf_buffer_append(&locbuf, "-uninstalled" PKG_CONFIG_EXT);
+
+		pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&locbuf), PKGCONF_PKG_PROPF_UNINSTALLED);
 	}
-	else if ((f = fopen(locbuf, "r")) != NULL)
+
+	if (pkg == NULL)
 	{
-		PKGCONF_TRACE(client, "found: %s", locbuf);
-		pkg = pkgconf_pkg_new_from_file(client, locbuf, f, 0);
+		pkgconf_buffer_rewind(&locbuf);
+		pkgconf_buffer_append(&locbuf, path);
+		pkgconf_buffer_push_byte(&locbuf, PKG_DIR_SEP_S);
+		pkgconf_buffer_append(&locbuf, name);
+		pkgconf_buffer_append(&locbuf, PKG_CONFIG_EXT);
+
+		pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&locbuf), 0);
 	}
 
+	if (pkg != NULL)
+		PKGCONF_TRACE(client, "found%s: %s", pkg->flags & PKGCONF_PKG_PROPF_UNINSTALLED ? " (uninstalled)" : "", pkgconf_buffer_str(&locbuf));
+
+	pkgconf_buffer_finalize(&locbuf);
+
 	return pkg;
 }
 
@@ -718,26 +1004,49 @@ pkgconf_pkg_scan_dir(pkgconf_client_t *c
 
 	for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir))
 	{
-		char filebuf[PKGCONF_ITEM_SIZE];
-		pkgconf_pkg_t *pkg;
-		FILE *f;
-
-		pkgconf_strlcpy(filebuf, path, sizeof filebuf);
-		pkgconf_strlcat(filebuf, "/", sizeof filebuf);
-		pkgconf_strlcat(filebuf, dirent->d_name, sizeof filebuf);
+		pkgconf_buffer_t filebuf = PKGCONF_BUFFER_INITIALIZER;
+		pkgconf_pkg_t *pkg = NULL;
 
-		if (!str_has_suffix(filebuf, PKG_CONFIG_EXT))
+		if (!pkgconf_buffer_join(&filebuf, '/', path, dirent->d_name, NULL))
+		{
+			closedir(dir);
+			return NULL;
+		}
+
+		if (!str_has_suffix(pkgconf_buffer_str(&filebuf), PKG_CONFIG_EXT))
+		{
+			pkgconf_buffer_finalize(&filebuf);
 			continue;
+		}
 
-		PKGCONF_TRACE(client, "trying file [%s]", filebuf);
+		if (!(client->flags & PKGCONF_PKG_PKGF_NO_CACHE))
+		{
+			pkgconf_buffer_t idbuf = PKGCONF_BUFFER_INITIALIZER;
 
-		f = fopen(filebuf, "r");
-		if (f == NULL)
-			continue;
+			if (pkgconf_buffer_append(&idbuf, pkgconf_path_find_basename(pkgconf_buffer_str(&filebuf))))
+			{
+				char *idptr = strrchr(pkgconf_buffer_str(&idbuf), '.');
+
+				if (idptr != NULL)
+					*idptr = '\0';
+
+				pkg = pkgconf_cache_lookup(client, pkgconf_buffer_str(&idbuf));
+			}
+
+			pkgconf_buffer_finalize(&idbuf);
+		}
+
+		PKGCONF_TRACE(client, "trying file [%s]", pkgconf_buffer_str(&filebuf));
+
+		if (pkg == NULL)
+			pkg = pkgconf_pkg_new_from_path(client, pkgconf_buffer_str(&filebuf), 0);
+		pkgconf_buffer_finalize(&filebuf);
 
-		pkg = pkgconf_pkg_new_from_file(client, filebuf, f, 0);
 		if (pkg != NULL)
 		{
+			if (!(pkg->flags & PKGCONF_PKG_PROPF_CACHED) && !(client->flags & PKGCONF_PKG_PKGF_NO_CACHE))
+				pkgconf_cache_add(client, pkg);
+
 			if (func(pkg, data))
 			{
 				outpkg = pkg;
@@ -774,6 +1083,22 @@ pkgconf_scan_all(pkgconf_client_t *clien
 	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;
@@ -787,43 +1112,24 @@ pkgconf_scan_all(pkgconf_client_t *clien
 	return NULL;
 }
 
-#ifdef _WIN32
 static pkgconf_pkg_t *
-pkgconf_pkg_find_in_registry_key(pkgconf_client_t *client, HKEY hkey, const char *name)
+search_preload_list(pkgconf_client_t *client, const char *name)
 {
-	pkgconf_pkg_t *pkg = NULL;
-
-	HKEY key;
-	int i = 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 NULL;
+	pkgconf_node_t *n;
 
-	while (RegEnumValue(key, i++, buf, &bufsize, NULL, NULL, NULL, NULL)
-			== ERROR_SUCCESS)
+	PKGCONF_FOREACH_LIST_ENTRY(client->preloaded_pkgs.head, n)
 	{
-		char pathbuf[PKGCONF_ITEM_SIZE];
-		DWORD type;
-		DWORD pathbuflen = sizeof pathbuf;
+		pkgconf_pkg_t *pkg = n->data;
 
-		if (RegQueryValueEx(key, buf, NULL, &type, (LPBYTE) pathbuf, &pathbuflen)
-				== ERROR_SUCCESS && type == REG_SZ)
+		if (!strcmp(pkg->id, name))
 		{
-			pkg = pkgconf_pkg_try_specific_path(client, pathbuf, name);
-			if (pkg != NULL)
-				break;
+			pkgconf_pkg_ref(client, pkg);
+			return pkg;
 		}
-
-		bufsize = sizeof buf;
 	}
 
-	RegCloseKey(key);
-	return pkg;
+	return NULL;
 }
-#endif
 
 /*
  * !doc
@@ -842,33 +1148,22 @@ pkgconf_pkg_find(pkgconf_client_t *clien
 {
 	pkgconf_pkg_t *pkg = NULL;
 	pkgconf_node_t *n;
-	FILE *f;
 
 	PKGCONF_TRACE(client, "looking for: %s", name);
 
 	/* name might actually be a filename. */
 	if (str_has_suffix(name, PKG_CONFIG_EXT))
 	{
-		if ((f = fopen(name, "r")) != NULL)
+		pkg = pkgconf_pkg_new_from_path(client, name, 0);
+		if (pkg != NULL)
 		{
 			PKGCONF_TRACE(client, "%s is a file", name);
 
-			pkg = pkgconf_pkg_new_from_file(client, name, f, 0);
-			if (pkg != NULL)
-			{
-				pkgconf_path_add(pkg->pc_filedir, &client->dir_list, true);
-				goto out;
-			}
+			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))
 	{
@@ -879,6 +1174,13 @@ pkgconf_pkg_find(pkgconf_client_t *clien
 		}
 	}
 
+	/* 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;
@@ -888,253 +1190,12 @@ pkgconf_pkg_find(pkgconf_client_t *clien
 			goto out;
 	}
 
-#ifdef _WIN32
-	/* support getting PKG_CONFIG_PATH from registry */
-	pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_CURRENT_USER, name);
-	if (!pkg)
-		pkg = pkgconf_pkg_find_in_registry_key(client, HKEY_LOCAL_MACHINE, name);
-#endif
-
 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 {
@@ -1249,6 +1310,9 @@ pkgconf_pkg_get_comparator(const pkgconf
 pkgconf_pkg_comparator_t
 pkgconf_pkg_comparator_lookup_by_name(const char *name)
 {
+	if (name == NULL)
+		return PKGCONF_CMP_ANY;
+
 	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);
@@ -1269,10 +1333,10 @@ static const pkgconf_pkg_provides_vermat
 	[PKGCONF_CMP_ANY] = {
 		.rulecmp = {
 			[PKGCONF_CMP_ANY]			= pkgconf_pkg_comparator_none,
-                },
+		},
 		.depcmp = {
 			[PKGCONF_CMP_ANY]			= pkgconf_pkg_comparator_none,
-                },
+		},
 	},
 	[PKGCONF_CMP_LESS_THAN] = {
 		.rulecmp = {
@@ -1346,7 +1410,7 @@ static const pkgconf_pkg_provides_vermat
 		},
 		.depcmp = {
 			[PKGCONF_CMP_ANY]			= pkgconf_pkg_comparator_none,
-                },
+		},
 	},
 	[PKGCONF_CMP_NOT_EQUAL] = {
 		.rulecmp = {
@@ -1360,7 +1424,7 @@ static const pkgconf_pkg_provides_vermat
 		},
 		.depcmp = {
 			[PKGCONF_CMP_ANY]			= pkgconf_pkg_comparator_none,
-                },
+		},
 	},
 };
 
@@ -1377,11 +1441,11 @@ pkgconf_pkg_scan_provides_vercmp(const p
 	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))
+		!rule->depcmp[provider->compare](provider->version, pkgdep->version))
 		return false;
 
 	if (rule->rulecmp[provider->compare] != NULL &&
-	    !rule->rulecmp[provider->compare](pkgdep->version, provider->version))
+		!rule->rulecmp[provider->compare](pkgdep->version, provider->version))
 		return false;
 
 	return true;
@@ -1393,8 +1457,9 @@ pkgconf_pkg_scan_provides_vercmp(const p
  * 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)
+pkgconf_pkg_scan_provides_entry(const pkgconf_pkg_t *pkg, void *data)
 {
+	const pkgconf_pkg_scan_providers_ctx_t *ctx = data;
 	const pkgconf_dependency_t *pkgdep = ctx->pkgdep;
 	pkgconf_node_t *node;
 
@@ -1421,7 +1486,7 @@ pkgconf_pkg_scan_providers(pkgconf_clien
 		.pkgdep = pkgdep,
 	};
 
-	pkg = pkgconf_scan_all(client, &ctx, (pkgconf_pkg_iteration_func_t) pkgconf_pkg_scan_provides_entry);
+	pkg = pkgconf_scan_all(client, &ctx, pkgconf_pkg_scan_provides_entry);
 	if (pkg != NULL)
 	{
 		pkgdep->match = pkgconf_pkg_ref(client, pkg);
@@ -1522,7 +1587,7 @@ pkgconf_pkg_report_graph_error(pkgconf_c
 {
 	if (eflags & PKGCONF_PKG_ERRF_PACKAGE_NOT_FOUND)
 	{
-		if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS) & !client->already_sent_notice)
+		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);
@@ -1553,6 +1618,18 @@ pkgconf_pkg_report_graph_error(pkgconf_c
 	return eflags;
 }
 
+static inline bool
+missing_node_is_tolerable(const pkgconf_client_t *client, const pkgconf_dependency_t *dep)
+{
+	if (!(dep->flags & PKGCONF_PKG_DEPF_INTERNAL))
+		return false;
+
+	if ((client->flags & PKGCONF_PKG_PKGF_REQUIRE_INTERNAL))
+		return false;
+
+	return true;
+}
+
 static inline unsigned int
 pkgconf_pkg_walk_list(pkgconf_client_t *client,
 	pkgconf_pkg_t *parent,
@@ -1560,7 +1637,8 @@ pkgconf_pkg_walk_list(pkgconf_client_t *
 	pkgconf_pkg_traverse_func_t func,
 	void *data,
 	int depth,
-	unsigned int skip_flags)
+	unsigned int skip_flags,
+	unsigned int iter_flags)
 {
 	unsigned int eflags = PKGCONF_PKG_ERRF_OK;
 	pkgconf_node_t *node, *next;
@@ -1577,20 +1655,22 @@ pkgconf_pkg_walk_list(pkgconf_client_t *
 			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))
+		if (eflags_local != PKGCONF_PKG_ERRF_OK)
 		{
-			pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local);
+			if (missing_node_is_tolerable(client, depnode))
+				continue;
+
+			if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_ERRORS))
+				pkgconf_pkg_report_graph_error(client, parent, pkgdep, depnode, eflags_local);
+
+			eflags |= 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
+			 * We break that by deleting 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
@@ -1599,10 +1679,11 @@ pkgconf_pkg_walk_list(pkgconf_client_t *
 			 * 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))
+				!(depnode->flags & PKGCONF_PKG_DEPF_SHARED) &&
+				!(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);
+					parent->id, parent->id, pkgdep->id, parent->id);
 
 				pkgconf_node_delete(node, deplist);
 				pkgconf_dependency_unref(client, depnode);
@@ -1616,7 +1697,7 @@ pkgconf_pkg_walk_list(pkgconf_client_t *
 
 		pkgconf_audit_log_dependency(client, pkgdep, depnode);
 
-		eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags);
+		eflags |= pkgconf_pkg_traverse_main(client, pkgdep, func, data, depth - 1, skip_flags, iter_flags);
 next:
 		pkgconf_pkg_unref(client, pkgdep);
 	}
@@ -1626,7 +1707,7 @@ next:
 	return eflags;
 }
 
-static inline unsigned int
+unsigned int
 pkgconf_pkg_walk_conflicts_list(pkgconf_client_t *client,
 	pkgconf_pkg_t *root, pkgconf_list_t *deplist)
 {
@@ -1652,7 +1733,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_
 			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),
+					pkgdep->version, pkgdep->id, parentnode->why, parentnode->package, pkgconf_pkg_get_comparator(parentnode),
 					parentnode->version != NULL ? " " : "", parentnode->version != NULL ? parentnode->version : "");
 
 				if (!(client->flags & PKGCONF_PKG_PKGF_SIMPLIFY_ERRORS))
@@ -1676,7 +1757,7 @@ pkgconf_pkg_walk_conflicts_list(pkgconf_
 /*
  * !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)
+ * .. c:function:: 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)
  *
  *    Walk and resolve the dependency graph up to `maxdepth` levels.
  *
@@ -1695,7 +1776,8 @@ pkgconf_pkg_traverse_main(pkgconf_client
 	pkgconf_pkg_traverse_func_t func,
 	void *data,
 	int maxdepth,
-	unsigned int skip_flags)
+	unsigned int skip_flags,
+	unsigned int iter_flags)
 {
 	unsigned int eflags = PKGCONF_PKG_ERRF_OK;
 
@@ -1712,12 +1794,12 @@ pkgconf_pkg_traverse_main(pkgconf_client
 	if (root->identifier == 0)
 		root->identifier = ++client->identifier;
 
-	PKGCONF_TRACE(client, "%s: level %d, serial %"PRIu64, root->id, maxdepth, client->serial);
+	PKGCONF_TRACE(client, "%s: level %d, serial %llu", root->id, maxdepth, (unsigned long long) 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);
+			func(client, root, data, iter_flags);
 	}
 
 	if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS) && root->conflicts.head != NULL)
@@ -1730,17 +1812,22 @@ pkgconf_pkg_traverse_main(pkgconf_client
 	}
 
 	PKGCONF_TRACE(client, "%s: walking 'Requires' list", root->id);
-	eflags = pkgconf_pkg_walk_list(client, root, &root->required, func, data, maxdepth, skip_flags);
+	eflags = pkgconf_pkg_walk_list(client, root, &root->required, func, data, maxdepth, skip_flags, iter_flags);
 	if (eflags != PKGCONF_PKG_ERRF_OK)
 		return eflags;
 
-	PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id);
+	if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS))
+	{
+		PKGCONF_TRACE(client, "%s: walking 'Requires.shared' list", root->id);
+
+		eflags = pkgconf_pkg_walk_list(client, root, &root->requires_shared, func, data, maxdepth, skip_flags, iter_flags);
+		if (eflags != PKGCONF_PKG_ERRF_OK)
+			return eflags;
+	}
 
-	/* 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;
+	PKGCONF_TRACE(client, "%s: walking 'Requires.private' list", root->id);
 
+	eflags = pkgconf_pkg_walk_list(client, root, &root->requires_private, func, data, maxdepth, skip_flags, iter_flags | PKGCONF_PKG_ITERF_PRIVATE);
 	if (eflags != PKGCONF_PKG_ERRF_OK)
 		return eflags;
 
@@ -1755,38 +1842,62 @@ pkgconf_pkg_traverse(pkgconf_client_t *c
 	int maxdepth,
 	unsigned int skip_flags)
 {
-	if (root->flags & PKGCONF_PKG_PROPF_VIRTUAL)
-		client->serial++;
+	client->serial++;
 
 	if ((client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) == 0)
+	{
 		skip_flags |= PKGCONF_PKG_DEPF_PRIVATE;
+	}
+
+	if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)
+		// Skip shared deps in static mode
+		skip_flags |= PKGCONF_PKG_DEPF_SHARED;
 
-	return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags);
+	return pkgconf_pkg_traverse_main(client, root, func, data, maxdepth, skip_flags, PKGCONF_PKG_ITERF_NONE);
 }
 
 static void
-pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+pkgconf_pkg_cflags_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
 {
-	pkgconf_list_t *list = data;
+	pkgconf_fragment_cursor_t *cursor = data;
 	pkgconf_node_t *node;
 
+	(void) iter_flags;
+
 	PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags.head, node)
 	{
 		pkgconf_fragment_t *frag = node->data;
-		pkgconf_fragment_copy(client, list, frag, false);
+		pkgconf_fragment_copy_cursor(client, cursor, frag, false);
 	}
 }
 
 static void
-pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+pkgconf_pkg_cflags_private_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
 {
-	pkgconf_list_t *list = data;
+	pkgconf_fragment_cursor_t *cursor = data;
 	pkgconf_node_t *node;
 
+	(void) iter_flags;
+
 	PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_private.head, node)
 	{
 		pkgconf_fragment_t *frag = node->data;
-		pkgconf_fragment_copy(client, list, frag, true);
+		pkgconf_fragment_copy_cursor(client, cursor, frag, true);
+	}
+}
+
+static void
+pkgconf_pkg_cflags_shared_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	pkgconf_fragment_cursor_t *cursor = data;
+	pkgconf_node_t *node;
+
+	(void) iter_flags;
+
+	PKGCONF_FOREACH_LIST_ENTRY(pkg->cflags_shared.head, node)
+	{
+		pkgconf_fragment_t *frag = node->data;
+		pkgconf_fragment_copy_cursor(client, cursor, frag, true);
 	}
 }
 
@@ -1810,11 +1921,25 @@ pkgconf_pkg_cflags(pkgconf_client_t *cli
 	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;
+	pkgconf_fragment_cursor_t cursor;
+
+	pkgconf_fragment_cursor_init(&cursor, &frags);
+
+	eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &cursor, maxdepth, skip_flags);
 
-	eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_collect, &frags, maxdepth, skip_flags);
+	if (eflag == PKGCONF_PKG_ERRF_OK)
+	{
+		if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)
+		{
+			eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_private_collect, &cursor, maxdepth, skip_flags);
+		}
+		else
+		{
+			eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_cflags_shared_collect, &cursor, 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);
+	pkgconf_fragment_cursor_deinit(&cursor);
 
 	if (eflag != PKGCONF_PKG_ERRF_OK)
 	{
@@ -1822,16 +1947,15 @@ pkgconf_pkg_cflags(pkgconf_client_t *cli
 		return eflag;
 	}
 
-	pkgconf_fragment_copy_list(client, list, &frags);
-	pkgconf_fragment_free(&frags);
+	pkgconf_list_splice(list, &frags);
 
 	return eflag;
 }
 
 static void
-pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+pkgconf_pkg_libs_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
 {
-	pkgconf_list_t *list = data;
+	pkgconf_fragment_cursor_t *cursor = data;
 	pkgconf_node_t *node;
 
 	if (!(client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) && pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE)
@@ -1840,7 +1964,7 @@ pkgconf_pkg_libs_collect(pkgconf_client_
 	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);
+		pkgconf_fragment_copy_cursor(client, cursor, frag, (iter_flags & PKGCONF_PKG_ITERF_PRIVATE) != 0);
 	}
 
 	if (client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS)
@@ -1848,7 +1972,15 @@ pkgconf_pkg_libs_collect(pkgconf_client_
 		PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_private.head, node)
 		{
 			pkgconf_fragment_t *frag = node->data;
-			pkgconf_fragment_copy(client, list, frag, true);
+			pkgconf_fragment_copy_cursor(client, cursor, frag, true);
+		}
+	}
+	else
+	{
+		PKGCONF_FOREACH_LIST_ENTRY(pkg->libs_shared.head, node)
+		{
+			pkgconf_fragment_t *frag = node->data;
+			pkgconf_fragment_copy_cursor(client, cursor, frag, true);
 		}
 	}
 }
@@ -1871,12 +2003,85 @@ unsigned int
 pkgconf_pkg_libs(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
 {
 	unsigned int eflag;
+	pkgconf_fragment_cursor_t cursor;
 
-	eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, list, maxdepth, 0);
+	pkgconf_fragment_cursor_init(&cursor, list);
+	eflag = pkgconf_pkg_traverse(client, root, pkgconf_pkg_libs_collect, &cursor, maxdepth, 0);
+	pkgconf_fragment_cursor_deinit(&cursor);
 
 	if (eflag != PKGCONF_PKG_ERRF_OK)
 	{
 		pkgconf_fragment_free(list);
+		return eflag;
+	}
+
+	return eflag;
+}
+
+static void
+pkgconf_pkg_link_abi_collect(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
+{
+	pkgconf_list_t *list = data;
+	pkgconf_node_t *node;
+
+	(void) iter_flags;
+
+	if (!(client->flags & PKGCONF_PKG_PKGF_SEARCH_PRIVATE) && pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE)
+		return;
+
+	PKGCONF_FOREACH_LIST_ENTRY(pkg->link_abi.head, node)
+	{
+		pkgconf_bufferset_t *tag = node->data;
+		pkgconf_node_t *iter;
+		bool seen = false;
+
+		PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
+		{
+			pkgconf_bufferset_t *existing = iter->data;
+
+			if (pkgconf_buffer_match(&existing->buffer, &tag->buffer))
+			{
+				seen = true;
+				break;
+			}
+		}
+
+		if (!seen)
+			pkgconf_bufferset_extend(list, &tag->buffer);
+	}
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: int pkgconf_pkg_link_abi(pkgconf_client_t *client, pkgconf_pkg_t *root, pkgconf_list_t *list, int maxdepth)
+ *
+ *    Walks a dependency graph and collects the union of ``Link.ABI`` tags.
+ *
+ *    The tags describe the ABI a consumer must link the module against.  They
+ *    are gathered over the same closure as ``LIBS``: the module's own tags and
+ *    those of its public ``Requires`` always contribute, while ``Requires.private``
+ *    tags contribute only when private dependencies are being linked (i.e. a
+ *    static link).  Unlike a runtime library load, ABI compatibility of the
+ *    exposed interface applies equally to shared and static linking.
+ *
+ *    :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 bufferset list to add the collected ``Link.ABI`` tags 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_link_abi(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_link_abi_collect, list, maxdepth, 0);
+
+	if (eflag != PKGCONF_PKG_ERRF_OK)
+	{
+		pkgconf_bufferset_free(list);
 		return eflag;
 	}
 
Index: libpkgconf/queue.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/queue.c,v
diff -u -p -r1.1.1.1 queue.c
--- libpkgconf/queue.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/queue.c	4 Aug 2026 16:20:54 -0000
@@ -2,6 +2,8 @@
  * queue.c
  * compilation of a list of packages into a world dependency set
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -32,6 +34,46 @@
 /*
  * !doc
  *
+ * .. c:function:: void pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep)
+ *
+ *    Pushes a requested dependency onto the dependency resolver's queue which is described by
+ *    a pkgconf_dependency_t node.
+ *
+ *    :param pkgconf_list_t* list: the dependency resolution queue to add the package request to.
+ *    :param pkgconf_dependency_t* dep: the dependency requested
+ *    :return: nothing
+ */
+void
+pkgconf_queue_push_dependency(pkgconf_list_t *list, const pkgconf_dependency_t *dep)
+{
+	pkgconf_buffer_t depbuf = PKGCONF_BUFFER_INITIALIZER;
+	pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t));
+
+	if (pkgq == NULL)
+		return;
+
+	if (!pkgconf_buffer_append(&depbuf, dep->package) ||
+		(dep->version != NULL &&
+		 !pkgconf_buffer_append_fmt(&depbuf, " %s %s", pkgconf_pkg_get_comparator(dep), dep->version)))
+	{
+		pkgconf_buffer_finalize(&depbuf);
+		free(pkgq);
+		return;
+	}
+
+	pkgq->package = pkgconf_buffer_freeze(&depbuf);
+	if (pkgq->package == NULL)
+	{
+		free(pkgq);
+		return;
+	}
+
+	pkgconf_node_insert_tail(&pkgq->iter, pkgq, list);
+}
+
+/*
+ * !doc
+ *
  * .. c:function:: void pkgconf_queue_push(pkgconf_list_t *list, const char *package)
  *
  *    Pushes a requested dependency onto the dependency resolver's queue.
@@ -45,7 +87,16 @@ pkgconf_queue_push(pkgconf_list_t *list,
 {
 	pkgconf_queue_t *pkgq = calloc(1, sizeof(pkgconf_queue_t));
 
+	if (pkgq == NULL)
+		return;
+
 	pkgq->package = strdup(package);
+	if (pkgq->package == NULL)
+	{
+		free(pkgq);
+		return;
+	}
+
 	pkgconf_node_insert_tail(&pkgq->iter, pkgq, list);
 }
 
@@ -103,8 +154,10 @@ pkgconf_queue_free(pkgconf_list_t *list)
 }
 
 static void
-pkgconf_queue_mark_public(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
+pkgconf_queue_mark_public(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data, unsigned int iter_flags)
 {
+	(void) iter_flags;
+
 	if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE)
 	{
 		pkgconf_list_t *list = data;
@@ -127,13 +180,15 @@ static unsigned int
 pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client,
 	pkgconf_pkg_t *root,
 	void *data,
-	int maxdepth);
+	int maxdepth,
+	unsigned int iter_flags);
 
 static inline unsigned int
 pkgconf_queue_collect_dependencies_walk(pkgconf_client_t *client,
 	pkgconf_list_t *deplist,
 	void *data,
-	int depth)
+	int depth,
+	unsigned int iter_flags)
 {
 	unsigned int eflags = PKGCONF_PKG_ERRF_OK;
 	pkgconf_node_t *node;
@@ -157,14 +212,20 @@ pkgconf_queue_collect_dependencies_walk(
 		if (pkg->serial == client->serial)
 			continue;
 
-		if (client->flags & PKGCONF_PKG_PKGF_ITER_PKG_IS_PRIVATE)
+		if (iter_flags & PKGCONF_PKG_ITERF_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);
+		eflags |= pkgconf_queue_collect_dependencies_main(client, pkg, data, depth - 1, iter_flags);
 
 		flattened_dep = pkgconf_dependency_copy(client, dep);
+		if (flattened_dep == NULL)
+		{
+			eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK;
+			continue;
+		}
+
 		pkgconf_node_insert(&flattened_dep->iter, flattened_dep, &world->required);
 	}
 
@@ -175,7 +236,8 @@ static unsigned int
 pkgconf_queue_collect_dependencies_main(pkgconf_client_t *client,
 	pkgconf_pkg_t *root,
 	void *data,
-	int maxdepth)
+	int maxdepth,
+	unsigned int iter_flags)
 {
 	unsigned int eflags = PKGCONF_PKG_ERRF_OK;
 
@@ -189,19 +251,24 @@ pkgconf_queue_collect_dependencies_main(
 
 	root->serial = client->serial;
 
+	if (!(client->flags & PKGCONF_PKG_PKGF_MERGE_PRIVATE_FRAGMENTS))
+	{
+		PKGCONF_TRACE(client, "%s: collecting shared dependencies, level %d", root->id, maxdepth);
+
+		eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_shared, data, maxdepth, iter_flags);
+		if (eflags != PKGCONF_PKG_ERRF_OK)
+			return eflags;
+	}
+
 	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;
+	eflags = pkgconf_queue_collect_dependencies_walk(client, &root->requires_private, data, maxdepth, iter_flags | PKGCONF_PKG_ITERF_PRIVATE);
 	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);
+	eflags = pkgconf_queue_collect_dependencies_walk(client, &root->required, data, maxdepth, iter_flags);
 	if (eflags != PKGCONF_PKG_ERRF_OK)
 		return eflags;
 
@@ -217,7 +284,58 @@ pkgconf_queue_collect_dependencies(pkgco
 	int maxdepth)
 {
 	++client->serial;
-	return pkgconf_queue_collect_dependencies_main(client, root, data, maxdepth);
+	return pkgconf_queue_collect_dependencies_main(client, root, data, maxdepth, PKGCONF_PKG_ITERF_NONE);
+}
+
+static inline unsigned int
+pkgconf_queue_collect_conflicts(pkgconf_client_t *client,
+	pkgconf_pkg_t *root,
+	pkgconf_pkg_t *world,
+	int maxdepth)
+{
+	unsigned int eflags = PKGCONF_PKG_ERRF_OK;
+	pkgconf_node_t *node;
+
+	PKGCONF_TRACE(client, "%s: collecting conflicts, level %d", root->id, maxdepth);
+
+	PKGCONF_FOREACH_LIST_ENTRY(root->required.head, node)
+	{
+		pkgconf_dependency_t *dep = node->data;
+		pkgconf_pkg_t *pkg = dep->match;
+		pkgconf_node_t *cnode;
+
+		if (*dep->package == '\0')
+			continue;
+
+		if (pkg == NULL)
+		{
+			PKGCONF_TRACE(client, "WTF: unmatched dependency %p <%s>", dep, dep->package);
+			continue;
+		}
+
+		PKGCONF_FOREACH_LIST_ENTRY(pkg->conflicts.head, cnode)
+		{
+			pkgconf_dependency_t *conflict = cnode->data;
+			pkgconf_dependency_t *flattened_conflict = pkgconf_dependency_copy(client, conflict);
+			if (flattened_conflict == NULL)
+			{
+				eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK;
+				continue;
+			}
+
+			flattened_conflict->why = strdup(pkg->id);
+			if (flattened_conflict->why == NULL)
+			{
+				eflags |= PKGCONF_PKG_ERRF_DEPGRAPH_BREAK;
+				pkgconf_dependency_unref(client, flattened_conflict);
+				continue;
+			}
+
+			pkgconf_node_insert(&flattened_conflict->iter, flattened_conflict, &world->conflicts);
+		}
+	}
+
+	return eflags;
 }
 
 static inline unsigned int
@@ -253,6 +371,13 @@ pkgconf_queue_verify(pkgconf_client_t *c
 		return result;
 	}
 
+	result = pkgconf_queue_collect_conflicts(client, 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");
@@ -267,6 +392,18 @@ pkgconf_queue_verify(pkgconf_client_t *c
 		}
 	}
 
+	if (!(client->flags & PKGCONF_PKG_PKGF_SKIP_CONFLICTS))
+	{
+		PKGCONF_TRACE(client, "checking for conflicts");
+
+		result = pkgconf_pkg_walk_conflicts_list(client, world, &world->conflicts);
+		if (result != PKGCONF_PKG_ERRF_OK)
+		{
+			pkgconf_solution_free(client, &initial_world);
+			return result;
+		}
+	}
+
 	/* free the initial solution */
 	pkgconf_solution_free(client, &initial_world);
 
@@ -293,6 +430,7 @@ pkgconf_solution_free(pkgconf_client_t *
 	{
 		pkgconf_dependency_free(&world->required);
 		pkgconf_dependency_free(&world->requires_private);
+		pkgconf_dependency_free(&world->conflicts);
 	}
 }
 
Index: libpkgconf/stdinc.h
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/stdinc.h,v
diff -u -p -r1.2 stdinc.h
--- libpkgconf/stdinc.h	9 May 2025 06:07:51 -0000	1.2
+++ libpkgconf/stdinc.h	4 Aug 2026 16:20:54 -0000
@@ -2,6 +2,8 @@
  * stdinc.h
  * pull in standard headers (including portability hacks)
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2012 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -16,6 +18,11 @@
 #ifndef LIBPKGCONF_STDINC_H
 #define LIBPKGCONF_STDINC_H
 
+/* make POSIX/BSD declarations (e.g. strdup) visible even under a strict -std= */
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -26,16 +33,35 @@
 #include <sys/types.h>
 #include <stdint.h>
 #include <errno.h>
+#include <limits.h>
 
 #ifdef _WIN32
 # define WIN32_LEAN_AND_MEAN
 # include <windows.h>
 # include <malloc.h>
+# include <io.h>     /* for _setmode() */
+# include <fcntl.h>
 # define PATH_DEV_NULL	"nul"
-# ifdef _WIN64
-#  define SIZE_FMT_SPECIFIER	"%I64u"
+# ifdef _MSC_VER
+#  if _MSC_VER >= 1900
+#   define SIZE_FMT_SPECIFIER "%zu"
+#  else
+#   ifdef _WIN64
+#    define SIZE_FMT_SPECIFIER "%I64u"
+#   else
+#    define SIZE_FMT_SPECIFIER "%u"
+#   endif
+#  endif
 # else
-#  define SIZE_FMT_SPECIFIER	"%u"
+#  ifdef _WIN64
+#   ifndef __MINGW32__
+#    define SIZE_FMT_SPECIFIER	"%I64u"
+#   else
+#    define SIZE_FMT_SPECIFIER	"%llu"
+#   endif
+#  else
+#   define SIZE_FMT_SPECIFIER	"%u"
+#  endif
 # endif
 # ifndef ssize_t
 # ifndef __MINGW32__
@@ -48,9 +74,14 @@
 # ifndef __MINGW32__
 #  include "win-dirent.h"
 # else
-# include <dirent.h>
+#  include <dirent.h>
 # endif
 # define PKGCONF_ITEM_SIZE (_MAX_PATH + 1024)
+# define PKG_CONFIG_PATH_SEP_S   ";"
+# define PKG_DIR_SEP_S   '\\'
+# define strcasecmp _stricmp
+# define strncasecmp _strnicmp
+# define realpath(N,R) _fullpath((R),(N),_MAX_PATH)
 #else
 # define PATH_DEV_NULL	"/dev/null"
 # define SIZE_FMT_SPECIFIER	"%zu"
@@ -59,13 +90,19 @@
 # endif
 # include <dirent.h>
 # include <unistd.h>
-# include <limits.h>
 # include <strings.h>
+# include <fcntl.h>    // open
+# include <libgen.h>   // basename/dirname
+# include <sys/stat.h> // lstat, S_ISLNK
+# include <unistd.h>   // close, readlinkat
+# include <string.h>
 # ifdef PATH_MAX
 #  define PKGCONF_ITEM_SIZE (PATH_MAX + 1024)
 # else
 #  define PKGCONF_ITEM_SIZE (4096 + 1024)
 # endif
+# define PKG_CONFIG_PATH_SEP_S   ":"
+# define PKG_DIR_SEP_S   '/'
 #endif
 
 #endif
Index: libpkgconf/tuple.c
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/libpkgconf/tuple.c,v
diff -u -p -r1.1.1.1 tuple.c
--- libpkgconf/tuple.c	3 May 2025 15:09:39 -0000	1.1.1.1
+++ libpkgconf/tuple.c	4 Aug 2026 16:30:47 -0000
@@ -2,6 +2,8 @@
  * tuple.c
  * management of key->value tuples
  *
+ * SPDX-License-Identifier: pkgconf
+ *
  * Copyright (c) 2011, 2012 pkgconf authors (see AUTHORS).
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -48,22 +50,6 @@ pkgconf_tuple_add_global(pkgconf_client_
 	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
  *
@@ -76,16 +62,21 @@ lookup_global_tuple(const pkgconf_client
  *    :return: the contents of the variable or ``NULL``
  *    :rtype: char *
  */
-char *
-pkgconf_tuple_find_global(const pkgconf_client_t *client, const char *key)
+const char *
+pkgconf_tuple_find_global(pkgconf_client_t *client, const char *key)
 {
-	pkgconf_tuple_t *tuple;
+	pkgconf_variable_t *v;
+	bool saw_sysroot = false;
 
-	tuple = lookup_global_tuple(client, key);
-	if (tuple == NULL)
+	if (client == NULL || key == NULL)
 		return NULL;
 
-	return tuple->value;
+	v = pkgconf_variable_find(&client->global_vars, key);
+
+	pkgconf_buffer_rewind(&client->_scratch_buffer);
+	(void) pkgconf_variable_eval(client, &client->global_vars, v, &client->_scratch_buffer, &saw_sysroot);
+
+	return pkgconf_buffer_str_or_empty(&client->_scratch_buffer);
 }
 
 /*
@@ -122,6 +113,9 @@ pkgconf_tuple_define_global(pkgconf_clie
 	char *value;
 	pkgconf_tuple_t *tuple;
 
+	if (workbuf == NULL)
+		goto out;
+
 	value = strchr(workbuf, '=');
 	if (value == NULL)
 		goto out;
@@ -136,23 +130,6 @@ 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)
 {
@@ -161,6 +138,9 @@ dequote(const char *value)
 	const char *i;
 	char quote = 0;
 
+	if (buf == NULL)
+		return NULL;
+
 	if (*value == '\'' || *value == '"')
 		quote = *value;
 
@@ -178,51 +158,12 @@ dequote(const char *value)
 	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.
+ *    Wrapper around pkgconf_variable_get_or_create(list, key) and bytecode compiler.
  *
  *    :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.
@@ -236,200 +177,122 @@ 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_buffer_t rhs_bcbuf = PKGCONF_BUFFER_INITIALIZER;
 
-	pkgconf_tuple_find_delete(list, key);
+	(void) client;
+
+	if (list == NULL || key == NULL || value == NULL)
+		return NULL;
 
 	dequote_value = dequote(value);
+	if (dequote_value == NULL)
+		return NULL;
 
-	tuple->key = strdup(key);
-	if (parse)
-		tuple->value = pkgconf_tuple_parse(client, list, dequote_value, flags);
-	else
-		tuple->value = strdup(dequote_value);
+	pkgconf_variable_t *v = pkgconf_variable_get_or_create(list, key);
+	if (v == NULL)
+	{
+		free(dequote_value);
+		return NULL;
+	}
+
+	if (!parse)
+	{
+		if (!pkgconf_bytecode_emit_text(&rhs_bcbuf, dequote_value, strlen(dequote_value)))
+		{
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			free(dequote_value);
+			return NULL;
+		}
 
-	PKGCONF_TRACE(client, "adding tuple to @%p: %s => %s (parsed? %d)", list, key, tuple->value, parse);
+		free(dequote_value);
+	}
+	else
+	{
+		if (!pkgconf_bytecode_compile(&rhs_bcbuf, dequote_value))
+		{
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			free(dequote_value);
+			return NULL;
+		}
 
-	pkgconf_node_insert(&tuple->iter, tuple, list);
+		free(dequote_value);
+	}
 
-	free(dequote_value);
+	/* ugh, we are doing var=${var}/foo stuff */
+	if (parse && pkgconf_bytecode_references_var(&rhs_bcbuf, key))
+	{
+		pkgconf_buffer_t old_bcbuf = PKGCONF_BUFFER_INITIALIZER;
+		pkgconf_buffer_t new_bcbuf = PKGCONF_BUFFER_INITIALIZER;
 
-	return tuple;
-}
+		/* preserve the old bytecode */
+		if (!pkgconf_buffer_copy(&v->bcbuf, &old_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			return NULL;
+		}
 
-/*
- * !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;
+		/* splice the selfrefs, using the old bytecode instead of ${var} */
+		if (!pkgconf_bytecode_rewrite_selfrefs(&new_bcbuf, &rhs_bcbuf, key, &old_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&new_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
 
-	global_tuple = lookup_global_tuple(client, key);
-	if (global_tuple != NULL && global_tuple->flags & PKGCONF_PKG_TUPLEF_OVERRIDE)
-		return global_tuple->value;
+			return NULL;
+		}
 
-	PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
-	{
-		pkgconf_tuple_t *tuple = node->data;
+		/* copy the spliced bytecode back to &rhs_bcbuf, replacing its contents */
+		if (!pkgconf_buffer_copy(&new_bcbuf, &rhs_bcbuf))
+		{
+			pkgconf_buffer_finalize(&old_bcbuf);
+			pkgconf_buffer_finalize(&new_bcbuf);
+			pkgconf_buffer_finalize(&rhs_bcbuf);
+			return NULL;
+		}
 
-		if (!strcmp(tuple->key, key))
-			return tuple->value;
+		pkgconf_buffer_finalize(&old_bcbuf);
+		pkgconf_buffer_finalize(&new_bcbuf);
 	}
 
-	if (global_tuple != NULL)
-		return global_tuple->value;
+	pkgconf_buffer_finalize(&v->bcbuf);
+	v->bcbuf = rhs_bcbuf;
+	v->flags = flags;
+	pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
 
-	return NULL;
+	return (pkgconf_tuple_t *) v;
 }
 
 /*
  * !doc
  *
- * .. c:function:: char *pkgconf_tuple_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags)
+ * .. c:function:: char *pkgconf_tuple_find(const pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
  *
- *    Parse an expression for variable substitution.
+ *    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 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
+ *    :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_parse(const pkgconf_client_t *client, pkgconf_list_t *vars, const char *value, unsigned int flags)
+const char *
+pkgconf_tuple_find(pkgconf_client_t *client, pkgconf_list_t *list, const char *key)
 {
-	char buf[PKGCONF_BUFSIZE];
-	const char *ptr;
-	char *bptr = buf;
+	pkgconf_variable_t *v;
 
-	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';
+	if (client == NULL || list == NULL || key == NULL)
+		return NULL;
 
-	/*
-	 * 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);
+	v = pkgconf_variable_find(list, key);
+	if (v == NULL)
+		v = pkgconf_variable_find(&client->global_vars, key);
 
-		pkgconf_strlcpy(cleanpath, buf + strlen(sysroot_dir), sizeof cleanpath);
-		pkgconf_path_relocate(cleanpath, sizeof cleanpath);
+	pkgconf_buffer_rewind(&client->_scratch_buffer);
 
-		return strdup(cleanpath);
-	}
+	(void) pkgconf_variable_eval(client, list, v, &client->_scratch_buffer, NULL);
 
-	return strdup(buf);
+	return pkgconf_buffer_str_or_empty(&client->_scratch_buffer);
 }
 
 /*
@@ -448,10 +311,7 @@ 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);
+	pkgconf_variable_free(tuple);
 }
 
 /*
Index: libpkgconf/variable.c
===================================================================
RCS file: libpkgconf/variable.c
diff -N libpkgconf/variable.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/variable.c	4 Aug 2026 16:30:47 -0000
@@ -0,0 +1,188 @@
+/*
+ * variable.c
+ * variable management
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2026 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/libpkgconf.h>
+#include <libpkgconf/stdinc.h>
+
+/*
+ * !doc
+ *
+ * libpkgconf `variable` module
+ * ============================
+ *
+ * The libpkgconf `variable` module contains the functions related to
+ * managing variables.  It replaces the old `tuple` module.
+ */
+
+pkgconf_variable_t *
+pkgconf_variable_new(const char *key)
+{
+	pkgconf_variable_t *v;
+	size_t keylen;
+
+	if (key == NULL)
+		return NULL;
+
+	keylen = strlen(key);
+	v = calloc(1, sizeof(*v) + keylen + 1);
+	if (v == NULL)
+		return NULL;
+
+	v->key = (char *)(v + 1);
+	memcpy(v->key, key, keylen + 1);
+
+	return v;
+}
+
+void
+pkgconf_variable_free(pkgconf_variable_t *v)
+{
+	if (v == NULL)
+		return;
+
+	pkgconf_buffer_finalize(&v->bcbuf);
+	free(v);
+}
+
+pkgconf_variable_t *
+pkgconf_variable_find(const pkgconf_list_t *vars, const char *key)
+{
+	const pkgconf_node_t *n;
+
+	if (vars == NULL || key == NULL)
+		return NULL;
+
+	PKGCONF_FOREACH_LIST_ENTRY(vars->head, n)
+	{
+		pkgconf_variable_t *v = n->data;
+
+		if (!strcmp(v->key, key))
+			return v;
+	}
+
+	return NULL;
+}
+
+pkgconf_variable_t *
+pkgconf_variable_get_or_create(pkgconf_list_t *vars, const char *key)
+{
+	pkgconf_variable_t *v;
+
+	if (vars == NULL || key == NULL)
+		return NULL;
+
+	v = pkgconf_variable_find(vars, key);
+	if (v != NULL)
+		return v;
+
+	v = pkgconf_variable_new(key);
+	if (v == NULL)
+		return NULL;
+
+	pkgconf_node_insert_tail(&v->iter, v, vars);
+
+	return v;
+}
+
+void
+pkgconf_variable_delete(pkgconf_list_t *vars, pkgconf_variable_t *v)
+{
+	if (vars == NULL || v == NULL)
+		return;
+
+	pkgconf_node_delete(&v->iter, vars);
+	pkgconf_variable_free(v);
+}
+
+void
+pkgconf_variable_list_free(pkgconf_list_t *vars)
+{
+	pkgconf_node_t *node, *tmp;
+
+	if (vars == NULL)
+		return;
+
+	PKGCONF_FOREACH_LIST_ENTRY_SAFE(vars->head, tmp, node)
+	{
+		pkgconf_variable_t *v = node->data;
+
+		pkgconf_node_delete(node, vars);
+		pkgconf_variable_free(v);
+	}
+}
+
+bool
+pkgconf_variable_eval(pkgconf_client_t *client,
+	const pkgconf_list_t *tuples,
+	const pkgconf_variable_t *v,
+	pkgconf_buffer_t *out,
+	bool *saw_sysroot)
+{
+	if (client == NULL || tuples == NULL || v == NULL || out == NULL)
+		return false;
+
+	return pkgconf_bytecode_eval(client, tuples, &v->bc, out, saw_sysroot);
+}
+
+char *
+pkgconf_variable_eval_str(pkgconf_client_t *client,
+	const pkgconf_list_t *tuples,
+	const pkgconf_variable_t *v,
+	bool *saw_sysroot)
+{
+	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
+
+	if (client == NULL || tuples == NULL || v == NULL)
+		return NULL;
+
+	if (!pkgconf_variable_eval(client, tuples, v, &out, saw_sysroot))
+	{
+		pkgconf_buffer_finalize(&out);
+		return NULL;
+	}
+
+	return pkgconf_buffer_freeze(&out);
+}
+
+char *
+pkgconf_variable_eval_name(pkgconf_client_t *client,
+	const pkgconf_list_t *vars,
+	const char *varname)
+{
+	pkgconf_buffer_t varbuf = PKGCONF_BUFFER_INITIALIZER;
+	const pkgconf_buffer_t *sysroot_dir = PKGCONF_BUFFER_FROM_STR(client->sysroot_dir);
+	bool saw_sysroot = false;
+	pkgconf_variable_t *v;
+
+	pkgconf_bytecode_eval_ctx_t ctx = {
+		.client = client,
+		.vars = vars,
+	};
+
+	v = pkgconf_bytecode_eval_lookup_var(&ctx, varname, strlen(varname));
+	(void) pkgconf_variable_eval(client, vars, v, &varbuf, &saw_sysroot);
+
+	if (!(client->flags & PKGCONF_PKG_PKGF_FDO_SYSROOT_RULES) &&
+		!saw_sysroot && pkgconf_path_is_plausible(&varbuf))
+	{
+		/* if sysroot is set, and value does not already begin with sysroot */
+		if (!pkgconf_buffer_has_prefix(&varbuf, sysroot_dir))
+			pkgconf_buffer_prepend(&varbuf, pkgconf_buffer_str_or_empty(sysroot_dir));
+	}
+
+	return pkgconf_buffer_freeze(&varbuf);
+}
Index: libpkgconf/version.c
===================================================================
RCS file: libpkgconf/version.c
diff -N libpkgconf/version.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libpkgconf/version.c	4 Aug 2026 16:20:54 -0000
@@ -0,0 +1,238 @@
+/*
+ * version.c
+ * version comparison
+ *
+ * SPDX-License-Identifier: pkgconf
+ *
+ * Copyright (c) 2011-2026 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 <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+
+typedef enum {
+	PKGCONF_VERSION_TOKEN_END = 0,
+	PKGCONF_VERSION_TOKEN_TILDE,
+	PKGCONF_VERSION_TOKEN_NUMERIC,
+	PKGCONF_VERSION_TOKEN_ALPHA
+} pkgconf_version_token_kind_t;
+
+typedef struct {
+	pkgconf_version_token_kind_t kind;
+	const char *start;
+	const char *end;
+} pkgconf_version_token_t;
+
+typedef struct {
+	const char *cur;
+} pkgconf_version_iter_t;
+
+static inline bool
+pkgconf_version_is_separator(unsigned char ch)
+{
+	return !isalnum(ch) && ch != '~';
+}
+
+static const char *
+pkgconf_version_skip_separators(const char *s)
+{
+	while (*s && pkgconf_version_is_separator((unsigned char)*s))
+		s++;
+
+	return s;
+}
+
+static pkgconf_version_token_t
+pkgconf_version_next_token(pkgconf_version_iter_t *it)
+{
+	pkgconf_version_token_t tok;
+	const char *s = pkgconf_version_skip_separators(it->cur);
+
+	tok.start = s;
+	tok.end = s;
+	tok.kind = PKGCONF_VERSION_TOKEN_END;
+
+	if (*s == '\0')
+	{
+		it->cur = s;
+		return tok;
+	}
+
+	if (*s == '~')
+	{
+		tok.kind = PKGCONF_VERSION_TOKEN_TILDE;
+		tok.end = s + 1;
+		it->cur = tok.end;
+		return tok;
+	}
+
+	if (isdigit((unsigned char)*s))
+	{
+		tok.kind = PKGCONF_VERSION_TOKEN_NUMERIC;
+		while (*tok.end && isdigit((unsigned char)*tok.end))
+			tok.end++;
+		it->cur = tok.end;
+		return tok;
+	}
+
+	/*
+	 * Having skipped separators and ruled out end-of-string, tilde and
+	 * digits, the only remaining possibility is alpha: isalnum(c) is by
+	 * definition isalpha(c) || isdigit(c).
+	 */
+	tok.kind = PKGCONF_VERSION_TOKEN_ALPHA;
+	while (*tok.end && isalpha((unsigned char)*tok.end))
+		tok.end++;
+	it->cur = tok.end;
+
+	return tok;
+}
+
+static int
+pkgconf_version_compare_numeric(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
+{
+	const char *ap = a->start;
+	const char *bp = b->start;
+	size_t alen, blen;
+	int ret;
+
+	while (ap < a->end && *ap == '0')
+		ap++;
+
+	while (bp < b->end && *bp == '0')
+		bp++;
+
+	alen = (size_t)(a->end - ap);
+	blen = (size_t)(b->end - bp);
+
+	if (alen > blen)
+		return 1;
+	if (alen < blen)
+		return -1;
+
+	if (alen == 0)
+		return 0;
+
+	ret = strncmp(ap, bp, alen);
+	if (ret < 0)
+		return -1;
+	if (ret > 0)
+		return 1;
+
+	return 0;
+}
+
+static int
+pkgconf_version_compare_alpha(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
+{
+	size_t alen = (size_t)(a->end - a->start);
+	size_t blen = (size_t)(b->end - b->start);
+	size_t len = alen < blen ? alen : blen;
+	int ret;
+
+	ret = strncmp(a->start, b->start, len);
+	if (ret < 0)
+		return -1;
+	if (ret > 0)
+		return 1;
+
+	if (alen < blen)
+		return -1;
+	if (alen > blen)
+		return 1;
+
+	return 0;
+}
+
+static int
+pkgconf_version_compare_token(const pkgconf_version_token_t *a, const pkgconf_version_token_t *b)
+{
+	if (a->kind == PKGCONF_VERSION_TOKEN_TILDE || b->kind == PKGCONF_VERSION_TOKEN_TILDE)
+	{
+		if (a->kind != PKGCONF_VERSION_TOKEN_TILDE)
+			return 1;
+		if (b->kind != PKGCONF_VERSION_TOKEN_TILDE)
+			return -1;
+
+		return 0;
+	}
+
+	if (a->kind == PKGCONF_VERSION_TOKEN_END || b->kind == PKGCONF_VERSION_TOKEN_END)
+	{
+		if (a->kind == PKGCONF_VERSION_TOKEN_END && b->kind == PKGCONF_VERSION_TOKEN_END)
+			return 0;
+		if (a->kind == PKGCONF_VERSION_TOKEN_END)
+			return -1;
+
+		return 1;
+	}
+
+	/* left-side is numeric, beats any right-side non-numeric */
+	if (a->kind == PKGCONF_VERSION_TOKEN_NUMERIC)
+	{
+		if (b->kind != PKGCONF_VERSION_TOKEN_NUMERIC)
+			return 1;
+
+		return pkgconf_version_compare_numeric(a, b);
+	}
+
+	/* left-side is alpha, any right-side non-alpha wins */
+	if (b->kind != PKGCONF_VERSION_TOKEN_ALPHA)
+		return -1;
+
+	return pkgconf_version_compare_alpha(a, b);
+}
+
+/*
+ * !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)
+{
+	pkgconf_version_iter_t ia, ib;
+
+	if (a == NULL)
+		return -1;
+	if (b == NULL)
+		return 1;
+
+	if (!strcasecmp(a, b))
+		return 0;
+
+	ia.cur = a;
+	ib.cur = b;
+
+	for (;;)
+	{
+		pkgconf_version_token_t ta = pkgconf_version_next_token(&ia);
+		pkgconf_version_token_t tb = pkgconf_version_next_token(&ib);
+		int ret = pkgconf_version_compare_token(&ta, &tb);
+
+		if (ret != 0)
+			return ret;
+
+		if (ta.kind == PKGCONF_VERSION_TOKEN_END &&
+			tb.kind == PKGCONF_VERSION_TOKEN_END)
+		{
+			return 0;
+		}
+	}
+}
Index: man/pkgconf.1
===================================================================
RCS file: /cvs/src/usr.bin/pkgconf/man/pkgconf.1,v
diff -u -p -r1.12 pkgconf.1
--- man/pkgconf.1	7 Jun 2025 14:14:25 -0000	1.12
+++ man/pkgconf.1	4 Aug 2026 16:30:47 -0000
@@ -32,6 +32,22 @@ to build software that uses the librarie
 .Ar module
 arguments.
 .Pp
+When printing compiler and linker flags,
+.Nm
+escapes shell metacharacters
+.Pq such as quotes, whitespace, brackets, braces and glob characters
+in each fragment with a backslash.
+The output is intended to be re-tokenised by the consumer using POSIX shell
+word-splitting rules, as build systems generally do; under those rules the
+protective backslashes are removed and the original values are recovered.
+Note that pasting the output directly into an unquoted command substitution,
+for example
+.Ql cc $(pkg-config \-\-libs foo) ,
+performs word-splitting and pathname expansion but does
+.Em not
+remove the backslashes, so a value containing such characters may reach the
+toolchain with the escaping still present.
+.Pp
 The
 .Xr pc 5
 files are searched for along a path constructed from the
@@ -57,7 +73,6 @@ These options are, ordered by descending
 .Bl -enum
 .It
 No-module options:
-.Fl -relocate ,
 .Fl -dump-personality ,
 .Fl -about ,
 .Fl -version ,
@@ -79,6 +94,9 @@ command line and do not look at dependen
 Limited-output options:
 .Fl -validate ,
 .Fl -license ,
+.Fl -license-file ,
+.Fl -link-abi ,
+.Fl -source ,
 .Fl -uninstalled ,
 and
 .Fl -env :
@@ -120,7 +138,8 @@ General output options:
 .Fl -simulate ,
 .Fl -digraph ,
 .Fl -solution ,
-.Fl -fragment-tree :
+.Fl -fragment-tree ,
+.Fl -newlines :
 These options do not limit dependency resolution.
 .El
 .Pp
@@ -269,6 +288,19 @@ Most other options and all command line 
 Ignore
 .Sq Conflicts
 rules in modules.
+This option is implied by the options which merely report metadata about the
+requested modules rather than combining them into a build, namely
+.Fl -license ,
+.Fl -license-file ,
+.Fl -modversion ,
+.Fl -path ,
+.Fl -print-provides ,
+.Fl -print-requires ,
+.Fl -print-requires-private ,
+.Fl -print-variables ,
+.Fl -source ,
+and
+.Fl -variable .
 .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.
@@ -283,6 +315,29 @@ flags, or only the linker flags that are
 nor library flags, respectively.
 These options imply
 .Fl -print-errors .
+.It Fl -link-abi
+Print the set of link ABI tags
+.Pq for example Sy c No or Sy c++
+that the
+.Ar module
+must be linked against, derived from the
+.Sy Link.ABI
+properties found across its dependency closure.
+The tags from a package's own
+.Sy Link.ABI
+property and those of its public
+.Sy Requires
+always apply, while
+.Sy Requires.private
+tags apply only when linking statically
+.Pq Fl -static .
+A module with no link ABI requirements prints
+.Sy c .
+See
+.Xr pc 5
+for details of the
+.Sy Link.ABI
+property.
 .It Fl -list-all
 Walk the module search path in the order of descending priority.
 For each
@@ -342,6 +397,12 @@ is greater than the requested
 number.
 .It Fl -maximum-traverse-depth Ns = Ns Ar depth
 Impose a limit on the allowed depth in the dependency graph.
+The default
+.Ar depth
+is 256.
+Set
+.Ar depth
+to \-1 to allow unlimited traversal.
 For example, a
 .Ar depth
 of 2 restricts the resolver from acting on child
@@ -385,6 +446,9 @@ output.
 This option is only available if the preprocessor macro
 .Dv PKGCONF_LITE
 was not defined during compilation.
+.It Fl -newlines
+Use newlines to separate individual CFLAGS or linker flag fragments
+instead of spaces.
 .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.
@@ -475,10 +539,6 @@ This is mainly intended for use with the
 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
@@ -500,7 +560,9 @@ 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.
+Dumps a series of trees denoting
+.Nm Ns 's
+resolver state.
 This option is only available if the preprocessor macro
 .Dv PKGCONF_LITE
 was not defined during compilation.
@@ -580,9 +642,15 @@ in the same way as
 .It Ev DESTDIR
 If set to the same value as
 .Ev PKG_CONFIG_SYSROOT_DIR ,
-behave in the same way as if
+follow
 .Ev PKG_CONFIG_FDO_SYSROOT_RULES
-is set.
+for variable lookups, but disable automatic sysroot injection into
+.Fl I
+and
+.Fl L
+fragments to avoid prefixing paths with
+.Ev DESTDIR
+twice.
 If
 .Ev PKG_CONFIG_SYSROOT_DIR
 is not set or set to a different value,
@@ -652,6 +720,7 @@ For more details, see the
 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.
+Set this variable to \-1 to allow unlimited traversal.
 This variable overrides the
 .Fl -maximum-traverse-depth
 option, but is overridden by the other options mentioned there.
@@ -680,6 +749,13 @@ if any, and unless
 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
@@ -710,7 +786,6 @@ If not defined, the default list compile
 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 ,
@@ -730,7 +805,6 @@ If not defined, the default list compile
 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