summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorPierre Pronchery <khorben@FreeBSD.org>2026-06-24 19:47:19 +0000
committerPierre Pronchery <khorben@FreeBSD.org>2026-06-29 05:21:29 +0000
commitebcc78644e0cbc4141807c0887ef8d902cb91bb4 (patch)
treeda5efe7c3c9ae0f140e24a1b69833a476fb09f01 /cli
parent6294b6ab217a2d5f1d2bc23a64505a228294c508 (diff)
Diffstat (limited to 'cli')
-rw-r--r--cli/bomtool/main.c222
-rw-r--r--cli/core.c1464
-rw-r--r--cli/core.h103
-rw-r--r--cli/getopt_long.c3
-rw-r--r--cli/getopt_long.h2
-rw-r--r--cli/main.c1639
-rw-r--r--cli/renderer-msvc.c138
-rw-r--r--cli/renderer-msvc.h4
-rw-r--r--cli/spdxtool/core.c729
-rw-r--r--cli/spdxtool/core.h77
-rw-r--r--cli/spdxtool/main.c425
-rw-r--r--cli/spdxtool/serialize.c467
-rw-r--r--cli/spdxtool/serialize.h539
-rw-r--r--cli/spdxtool/simplelicensing.c113
-rw-r--r--cli/spdxtool/simplelicensing.h36
-rw-r--r--cli/spdxtool/software.c447
-rw-r--r--cli/spdxtool/software.h37
-rw-r--r--cli/spdxtool/util.c331
-rw-r--r--cli/spdxtool/util.h130
19 files changed, 5285 insertions, 1621 deletions
diff --git a/cli/bomtool/main.c b/cli/bomtool/main.c
index 1e8391aa5ae9..136d7c1ce2ca 100644
--- a/cli/bomtool/main.c
+++ b/cli/bomtool/main.c
@@ -2,6 +2,8 @@
* bomtool/main.c
* main() routine, printer functions
*
+ * SPDX-License-Identifier: pkgconf
+ *
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
* pkgconf authors (see AUTHORS).
*
@@ -14,6 +16,9 @@
* from the use of this software.
*/
+#include <ctype.h>
+#include <time.h>
+
#include "libpkgconf/config.h"
#include <libpkgconf/stdinc.h>
#include <libpkgconf/libpkgconf.h>
@@ -22,6 +27,8 @@
#define PKG_VERSION (((uint64_t) 1) << 1)
#define PKG_ABOUT (((uint64_t) 1) << 2)
#define PKG_HELP (((uint64_t) 1) << 3)
+#define PKG_OUTPUT (((uint64_t) 1) << 4)
+#define PKG_DEFINE_VARIABLE (((uint64_t) 1) << 5)
static const char *spdx_version = "SPDX-2.2";
static const char *bom_license = "CC0-1.0";
@@ -31,14 +38,39 @@ static pkgconf_client_t pkg_client;
static uint64_t want_flags;
static size_t maximum_package_count = 0;
static int maximum_traverse_depth = 2000;
-FILE *error_msgout = NULL;
+static FILE *error_msgout = NULL;
+static FILE *sbom_out = NULL;
+
+#define OUTPUT_OR_RET(client, f, fmt, ...) \
+ do { \
+ if (!pkgconf_output_file_fmt((f), (fmt), ##__VA_ARGS__)) { \
+ pkgconf_error((client), "bomtool: Could not output to file: %s", strerror(errno)); \
+ return; \
+ } \
+ } while (0)
+
+#define OUTPUT_OR_RET_FALSE(client, f, fmt, ...) \
+ do { \
+ if (!pkgconf_output_file_fmt((f), (fmt), ##__VA_ARGS__)) { \
+ pkgconf_error((client), "bomtool: Could not output to file: %s", strerror(errno)); \
+ return false; \
+ } \
+ } while (0)
+
+static const char *
+environ_lookup_handler(const pkgconf_client_t *client, const char *key)
+{
+ (void) client;
+
+ return getenv(key);
+}
static bool
error_handler(const char *msg, const pkgconf_client_t *client, void *data)
{
(void) client;
(void) data;
- fprintf(error_msgout, "%s", msg);
+ OUTPUT_OR_RET_FALSE(client, error_msgout, "%s", msg);
return true;
}
@@ -46,19 +78,32 @@ static const char *
sbom_spdx_identity(pkgconf_pkg_t *pkg)
{
static char buf[PKGCONF_ITEM_SIZE];
+ size_t i, o;
- snprintf(buf, sizeof buf, "%sC64%s", pkg->id, pkg->version);
-
+ /* Sanitize the package ID: only letters, numbers, dot (.) and dash (-)
+ * are allowed.
+ */
+ for (i = 0, o = 0; i < strlen(pkg->id) && o < sizeof(buf); i++, o++) {
+ char c = pkg->id[i];
+ if (c == '-' || c == '.' || isalnum(c))
+ buf[o] = c;
+ else {
+ snprintf(buf + o, sizeof(buf) - o, "C%02x", c);
+ o += 2;
+ }
+ }
+ snprintf(buf + o, sizeof(buf) - o, "C40%s", pkg->version);
+ /* ^^^ 0x40 is the at sign (@) */
return buf;
}
-static const char *
+static char *
sbom_name(pkgconf_pkg_t *world)
{
- static char buf[PKGCONF_BUFSIZE];
+ pkgconf_buffer_t name = PKGCONF_BUFFER_INITIALIZER;
pkgconf_node_t *node;
- pkgconf_strlcpy(buf, "SBOM-SPDX", sizeof buf);
+ pkgconf_buffer_append(&name, "SBOM-SPDX");
PKGCONF_FOREACH_LIST_ENTRY(world->required.head, node)
{
@@ -71,27 +116,49 @@ sbom_name(pkgconf_pkg_t *world)
if (!dep->match)
continue;
- pkgconf_strlcat(buf, "-", sizeof buf);
- pkgconf_strlcat(buf, sbom_spdx_identity(match), sizeof buf);
+ pkgconf_buffer_append_fmt(&name, "-%s", sbom_spdx_identity(match));
}
- return buf;
+ return pkgconf_buffer_freeze(&name);
}
-static void
+static bool
write_sbom_header(pkgconf_client_t *client, pkgconf_pkg_t *world)
{
- (void) client;
- (void) world;
+ time_t t;
+ struct tm *tm;
+ char buf[21];
+
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "SPDXVersion: %s\n", spdx_version);
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "DataLicense: %s\n", bom_license);
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "SPDXID: %s\n", document_ref);
+
+ char *docname = sbom_name(world);
+ if (!docname)
+ {
+ pkgconf_error(client, "write_sbom_header: out of memory");
+ return false;
+ }
+
+ if (!pkgconf_output_file_fmt(sbom_out, "DocumentName: %s\n", docname))
+ {
+ free(docname);
+ return false;
+ }
+
+ free(docname);
- printf("SPDXVersion: %s\n", spdx_version);
- printf("DataLicense: %s\n", bom_license);
- printf("SPDXID: %s\n", document_ref);
- printf("DocumentName: %s\n", sbom_name(world));
- printf("DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION);
- printf("Creator: Tool: bomtool %s\n", PACKAGE_VERSION);
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "DocumentNamespace: https://spdx.org/spdxdocs/bomtool-%s\n", PACKAGE_VERSION);
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "Creator: Tool: bomtool %s\n", PACKAGE_VERSION);
- printf("\n\n");
+ t = time(NULL);
+ tm = gmtime(&t);
+ strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", tm);
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "Created: %s\n", buf);
+
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "\n\n");
+
+ return true;
}
static const char *
@@ -104,39 +171,87 @@ sbom_identity(pkgconf_pkg_t *pkg)
return buf;
}
+static bool
+write_copyright_lines(pkgconf_client_t *client, const pkgconf_list_t *copyright_lines)
+{
+ const pkgconf_node_t *node;
+
+ if (copyright_lines->head == NULL) {
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "PackageCopyrightText: NOASSERTION\n");
+ return true;
+ }
+
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "PackageCopyrightText: <text>");
+
+ PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node)
+ {
+ const pkgconf_bufferset_t *set = node->data;
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "%s%s", pkgconf_buffer_str_or_empty(&set->buffer), node->prev != NULL ? "\n" : "");
+ }
+
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "</text>\n");
+
+ return true;
+}
+
static void
write_sbom_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
{
+ pkgconf_buffer_t license_buf = PKGCONF_BUFFER_INITIALIZER;
(void) client;
(void) unused;
if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
return;
- printf("##### Package: %s\n\n", sbom_identity(pkg));
+ OUTPUT_OR_RET(client, sbom_out, "##### Package: %s\n\n", sbom_identity(pkg));
+ OUTPUT_OR_RET(client, sbom_out, "PackageName: %s\n", sbom_identity(pkg));
+ OUTPUT_OR_RET(client, sbom_out, "SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg));
+ OUTPUT_OR_RET(client, sbom_out, "PackageVersion: %s\n", pkg->version);
+ OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: NOASSERTION\n");
- printf("PackageName: %s\n", sbom_identity(pkg));
- printf("SPDXID: SPDXRef-Package-%s\n", sbom_spdx_identity(pkg));
- printf("PackageVersion: %s\n", pkg->version);
- printf("PackageDownloadLocation: NOASSERTION\n");
- printf("PackageVerificationCode: NOASSERTION\n");
+ /* NOASSERTION is not a valide value for PackageVerificationCode. It
+ * expect 40 lowercase hexadecimal digits.
+ */
+#if 0
+ OUTPUT_OR_RET(client, sbom_out, "PackageVerificationCode: NOASSERTION\n");
+#endif
/* XXX: What about projects? */
if (pkg->maintainer != NULL)
- printf("PackageSupplier: Person: %s\n", pkg->maintainer);
+ OUTPUT_OR_RET(client, sbom_out, "PackageSupplier: Person: %s\n", pkg->maintainer);
if (pkg->url != NULL)
- printf("PackageHomePage: %s\n", pkg->url);
+ OUTPUT_OR_RET(client, sbom_out, "PackageHomePage: %s\n", pkg->url);
- printf("PackageLicenseDeclared: %s\n", pkg->license != NULL ? pkg->license : "NOASSERTION");
+ if (pkg->license.head != NULL)
+ {
+ pkgconf_license_render(client, &pkg->license, &license_buf);
+ bool ret = pkgconf_output_file_fmt(sbom_out, "PackageLicenseDeclared: %s\n", pkgconf_buffer_str_or_empty(&license_buf));
+ int errno_save = errno;
+ pkgconf_buffer_finalize(&license_buf);
+ if (!ret)
+ {
+ pkgconf_error(client, "bomtool: could not output to file: %s", strerror(errno_save));
+ return;
+ }
+ }
+ else
+ OUTPUT_OR_RET(client, sbom_out, "PackageLicenseDeclared: NOASSERTION\n");
+ OUTPUT_OR_RET(client, sbom_out, "PackageLicenseConcluded: NOASSERTION\n");
- if (pkg->copyright != NULL)
- printf("PackageCopyrightText: <text>%s</text>\n", pkg->copyright);
+ if (!write_copyright_lines(client, &pkg->copyright))
+ return;
if (pkg->description != NULL)
- printf("PackageSummary: <text>%s</text>\n", pkg->description);
+ OUTPUT_OR_RET(client, sbom_out, "PackageSummary: <text>%s</text>\n", pkg->description);
- printf("\n\n");
+ if (pkg->source != NULL)
+ OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: %s\n", pkg->source);
+ else
+ OUTPUT_OR_RET(client, sbom_out, "PackageDownloadLocation: NOASSERTION\n");
+
+ OUTPUT_OR_RET(client, sbom_out, "\n\n");
}
static void
@@ -151,7 +266,7 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu
if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
return;
- snprintf(baseref, sizeof baseref, "SPDXRef-Package-%sC64%s", pkg->id, pkg->version);
+ snprintf(baseref, sizeof baseref, "SPDXRef-Package-%s", sbom_spdx_identity(pkg));
PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
{
@@ -161,8 +276,8 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu
if (!dep->match)
continue;
- printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
- printf("Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
+ OUTPUT_OR_RET(client, sbom_out, "Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
+ OUTPUT_OR_RET(client, sbom_out, "Relationship: SPDXRef-Package-%s DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
}
PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node)
@@ -173,12 +288,12 @@ write_sbom_relationships(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unu
if (!dep->match)
continue;
- printf("Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
- printf("Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
+ OUTPUT_OR_RET(client, sbom_out, "Relationship: %s DEPENDS_ON SPDXRef-Package-%s\n", baseref, sbom_spdx_identity(match));
+ OUTPUT_OR_RET(client, sbom_out, "Relationship: SPDXRef-Package-%s DEV_DEPENDENCY_OF %s\n", sbom_spdx_identity(match), baseref);
}
if (pkg->required.head != NULL || pkg->requires_private.head != NULL)
- printf("\n\n");
+ OUTPUT_OR_RET(client, sbom_out, "\n\n");
}
static bool
@@ -187,7 +302,8 @@ generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world)
int eflag;
pkgconf_node_t *node;
- write_sbom_header(client, world);
+ if (!write_sbom_header(client, world))
+ return false;
eflag = pkgconf_pkg_traverse(client, world, write_sbom_package, NULL, maximum_traverse_depth, 0);
if (eflag != PKGCONF_PKG_ERRF_OK)
@@ -205,7 +321,7 @@ generate_sbom_from_world(pkgconf_client_t *client, pkgconf_pkg_t *world)
if (!dep->match)
continue;
- printf("Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match));
+ OUTPUT_OR_RET_FALSE(client, sbom_out, "Relationship: %s DESCRIBES SPDXRef-Package-%s\n", document_ref, sbom_spdx_identity(match));
}
return true;
@@ -244,6 +360,8 @@ usage(void)
printf(" --help this message\n");
printf(" --about print bomtool version and license to stdout\n");
printf(" --version print bomtool version to stdout\n");
+ printf(" --output FILE output SBOM text to FILE\n");
+ printf(" --define-variable=varname=value define variable 'varname' as 'value'\n");
return EXIT_SUCCESS;
}
@@ -262,11 +380,14 @@ main(int argc, char *argv[])
};
error_msgout = stderr;
+ sbom_out = stdout;
struct pkg_option options[] = {
{ "version", no_argument, &want_flags, PKG_VERSION, },
{ "about", no_argument, &want_flags, PKG_ABOUT, },
{ "help", no_argument, &want_flags, PKG_HELP, },
+ { "output", required_argument, NULL, PKG_OUTPUT, },
+ { "define-variable", required_argument, NULL, PKG_DEFINE_VARIABLE, },
{ NULL, 0, NULL, 0 }
};
@@ -274,6 +395,18 @@ main(int argc, char *argv[])
{
switch (ret)
{
+ case PKG_OUTPUT:
+ sbom_out = fopen(pkg_optarg, "w");
+ if (sbom_out == NULL)
+ {
+ pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ break;
+ case PKG_DEFINE_VARIABLE:
+ pkgconf_tuple_define_global(&pkg_client, pkg_optarg);
+ break;
case '?':
case ':':
return EXIT_FAILURE;
@@ -282,7 +415,7 @@ main(int argc, char *argv[])
}
}
- pkgconf_client_init(&pkg_client, error_handler, NULL, personality);
+ pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler);
/* we have determined what features we want most likely. in some cases, we override later. */
pkgconf_client_set_flags(&pkg_client, want_client_flags);
@@ -339,7 +472,7 @@ main(int argc, char *argv[])
if (pkgq.head == NULL)
{
- fprintf(stderr, "Please specify at least one package name on the command line.\n");
+ pkgconf_output_file_fmt(stderr, "Please specify at least one package name on the command line.\n");
ret = EXIT_FAILURE;
goto out;
}
@@ -359,6 +492,9 @@ main(int argc, char *argv[])
}
out:
+ if (sbom_out != stdout)
+ fclose(sbom_out);
+
pkgconf_solution_free(&pkg_client, &world);
pkgconf_queue_free(&pkgq);
pkgconf_cross_personality_deinit(personality);
diff --git a/cli/core.c b/cli/core.c
new file mode 100644
index 000000000000..abdbeb8a5167
--- /dev/null
+++ b/cli/core.c
@@ -0,0 +1,1464 @@
+/*
+ * 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)
+{
+ 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)
+{
+ (void) unused;
+
+ pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%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_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 char *
+variable_eval(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 (!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);
+}
+
+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 = variable_eval(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;
+
+ 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;
+
+ pkgconf_fragment_render_buf(&filtered_list, &render_buf, true, state->want_render_ops, (state->want_flags & PKG_NEWLINES) ? '\n' : ' ');
+ pkgconf_output_fmt(client->output, PKGCONF_OUTPUT_STDOUT, "%s='%s'\n",
+ prefix, pkgconf_buffer_str_or_empty(&render_buf));
+ pkgconf_buffer_finalize(&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;
+ 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(client, &filtered_list, &unfiltered_list, filter_cflags, NULL);
+ maybe_add_module_definitions(client, world, &filtered_list);
+
+ if (filtered_list.head == NULL)
+ goto out;
+
+ pkgconf_fragment_copy_list(client, 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(client, &filtered_list, &unfiltered_list, filter_libs, NULL);
+
+ if (filtered_list.head == NULL)
+ goto out;
+
+ pkgconf_fragment_copy_list(client, 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)
+{
+ 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) 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)
+{
+ pkgconf_buffer_t render_buf = PKGCONF_BUFFER_INITIALIZER;
+ (void) 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 */
+ 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);
+ 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_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)
+{
+ (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)
+{
+ (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;
+}
+
+void
+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)
+ pkgconf_buffer_push_byte(buffer, delim);
+
+ pkgconf_buffer_append(buffer, pn->path);
+ }
+}
+
+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");
+
+ PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n)
+ {
+ pkgconf_path_t *pn = n->data;
+
+ client->unveil_handler(client, pn->path, "r");
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(personality->dir_list.head, n)
+ {
+ pkgconf_path_t *pn = n->data;
+
+ client->unveil_handler(client, pn->path, "r");
+ }
+}
+
+/* SAFETY: pkgconf_client_t takes ownership of these 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;
+ path_list_to_buffer(&personality->dir_list, &pc_path_buf, ':');
+
+ pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER;
+ path_list_to_buffer(&personality->filter_libdirs, &pc_system_libdirs_buf, ':');
+
+ pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER;
+ path_list_to_buffer(&personality->filter_includedirs, &pc_system_includedirs_buf, ':');
+
+ pkgconf_pkg_t *pkg_config_virtual = calloc(1, sizeof(pkgconf_pkg_t));
+ if (pkg_config_virtual == NULL)
+ {
+ goto error;
+ }
+
+ pkg_config_virtual->owner = client;
+ pkg_config_virtual->id = strdup("pkg-config");
+ pkg_config_virtual->realname = strdup("pkg-config");
+ pkg_config_virtual->description = strdup("virtual package defining pkgconf API version supported");
+ pkg_config_virtual->url = strdup(PACKAGE_BUGREPORT);
+ pkg_config_virtual->version = strdup(PACKAGE_VERSION);
+
+ pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(&pc_system_libdirs_buf), false, 0);
+ pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(&pc_system_includedirs_buf), false, 0);
+ pkgconf_tuple_add(client, &pkg_config_virtual->vars, "pc_path", pkgconf_buffer_str_or_empty(&pc_path_buf), false, 0);
+
+ if (!pkgconf_client_preload_one(client, pkg_config_virtual))
+ {
+ goto error;
+ }
+
+ pkgconf_pkg_t *pkgconf_virtual = calloc(1, sizeof(pkgconf_pkg_t));
+ if (pkgconf_virtual == NULL)
+ {
+ goto error;
+ }
+
+ pkgconf_virtual->owner = client;
+ pkgconf_virtual->id = strdup("pkgconf");
+ pkgconf_virtual->realname = strdup("pkgconf");
+ pkgconf_virtual->description = strdup("virtual package defining pkgconf API version supported");
+ pkgconf_virtual->url = strdup(PACKAGE_BUGREPORT);
+ pkgconf_virtual->version = strdup(PACKAGE_VERSION);
+
+ pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_system_libdirs", pkgconf_buffer_str_or_empty(&pc_system_libdirs_buf), false, 0);
+ pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_system_includedirs", pkgconf_buffer_str_or_empty(&pc_system_includedirs_buf), false, 0);
+ pkgconf_tuple_add(client, &pkgconf_virtual->vars, "pc_path", pkgconf_buffer_str_or_empty(&pc_path_buf), false, 0);
+
+ if (!pkgconf_client_preload_one(client, pkgconf_virtual))
+ {
+ 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;
+ path_list_to_buffer(&p->dir_list, &pc_path_buf, ':');
+
+ pkgconf_buffer_t pc_system_libdirs_buf = PKGCONF_BUFFER_INITIALIZER;
+ path_list_to_buffer(&p->filter_libdirs, &pc_system_libdirs_buf, ':');
+
+ pkgconf_buffer_t pc_system_includedirs_buf = PKGCONF_BUFFER_INITIALIZER;
+ path_list_to_buffer(&p->filter_includedirs, &pc_system_includedirs_buf, ':');
+
+ 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");
+
+ 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.
+ */
+ if ((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)
+ state->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 ((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;
+ }
+ }
+
+ /* 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]);
+ 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);
+ }
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
+ {
+ pkgconf_dependency_t *dep = node->data;
+ 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);
+
+ 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);
+ 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);
+}
diff --git a/cli/core.h b/cli/core.h
new file mode 100644
index 000000000000..c6c5886e070e
--- /dev/null
+++ b/cli/core.h
@@ -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 void 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
diff --git a/cli/getopt_long.c b/cli/getopt_long.c
index c47215fae4cf..9bda0e1e32ef 100644
--- a/cli/getopt_long.c
+++ b/cli/getopt_long.c
@@ -1,7 +1,8 @@
/* $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
diff --git a/cli/getopt_long.h b/cli/getopt_long.h
index b1242915ab5b..b60a3f87b2c1 100644
--- a/cli/getopt_long.h
+++ b/cli/getopt_long.h
@@ -2,6 +2,8 @@
/* $FreeBSD$ */
/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
* Copyright (c) 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
diff --git a/cli/main.c b/cli/main.c
index 53eee4c87443..7959737b9913 100644
--- a/cli/main.c
+++ b/cli/main.c
@@ -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,97 @@
#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);
+#include "core.h"
- return false;
-}
-
-static bool
-print_package_entry(const pkgconf_pkg_t *entry, void *data)
+static const char *
+environ_lookup_handler(const pkgconf_client_t *client, const char *key)
{
- (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;
+ return getenv(key);
}
static bool
-filter_libs(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data)
+error_handler(const char *msg, const pkgconf_client_t *client, void *data)
{
- int got_flags = 0;
- (void) client;
(void) data;
+ pkgconf_cli_state_t *state = client->client_data;
- if (!(want_flags & PKG_KEEP_SYSTEM_LIBS) && pkgconf_fragment_has_system_dir(client, frag))
- return false;
-
- if (want_fragment_filter != NULL && (strchr(want_fragment_filter, frag->type) == NULL || !frag->type))
- return false;
-
- switch (frag->type)
- {
- case 'L': got_flags = PKG_LIBS_ONLY_LDPATH; break;
- case 'l': got_flags = PKG_LIBS_ONLY_LIBNAME; break;
- default: got_flags = PKG_LIBS_ONLY_OTHER; break;
- }
-
- return (want_flags & got_flags) != 0;
-}
-
-static void
-print_variables(pkgconf_pkg_t *pkg)
-{
- pkgconf_node_t *node;
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->vars.head, node)
- {
- pkgconf_tuple_t *tuple = node->data;
-
- printf("%s\n", tuple->key);
- }
-}
-
-static void
-print_requires(pkgconf_pkg_t *pkg)
-{
- pkgconf_node_t *node;
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
- {
- pkgconf_dependency_t *dep = node->data;
-
- printf("%s", dep->package);
-
- if (dep->version != NULL)
- printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
-
- printf("\n");
- }
-}
-
-static void
-print_requires_private(pkgconf_pkg_t *pkg)
-{
- pkgconf_node_t *node;
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node)
- {
- pkgconf_dependency_t *dep = node->data;
-
- printf("%s", dep->package);
-
- if (dep->version != NULL)
- printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
-
- printf("\n");
- }
-}
-
-static void
-print_provides(pkgconf_pkg_t *pkg)
-{
- pkgconf_node_t *node;
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->provides.head, node)
- {
- pkgconf_dependency_t *dep = node->data;
-
- printf("%s", dep->package);
-
- if (dep->version != NULL)
- printf(" %s %s", pkgconf_pkg_get_comparator(dep), dep->version);
-
- printf("\n");
- }
-}
-
-static bool
-apply_provides(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
-{
- pkgconf_node_t *iter;
- (void) client;
- (void) unused;
- (void) maxdepth;
-
- PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
- {
- pkgconf_dependency_t *dep = iter->data;
- pkgconf_pkg_t *pkg = dep->match;
-
- print_provides(pkg);
- }
-
- return true;
-}
-
-#ifndef PKGCONF_LITE
-static void
-print_digraph_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *data)
-{
- pkgconf_node_t *node;
- (void) client;
- pkgconf_pkg_t **last_seen = data;
-
- if(pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
- return;
-
- if (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE)
- printf("\"%s\" [fontname=Sans fontsize=8 fontcolor=gray color=gray]\n", pkg->id);
- else
- printf("\"%s\" [fontname=Sans fontsize=8]\n", pkg->id);
-
- if (last_seen != NULL)
- {
- if (*last_seen != NULL)
- printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=red]\n", (*last_seen)->id, pkg->id);
-
- *last_seen = pkg;
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
- {
- pkgconf_dependency_t *dep = node->data;
- const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package;
-
- if ((dep->flags & PKGCONF_PKG_DEPF_PRIVATE) == 0)
- printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg->id, dep_id);
- else
- printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id);
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(pkg->requires_private.head, node)
- {
- pkgconf_dependency_t *dep = node->data;
- const char *dep_id = (dep->match != NULL) ? dep->match->id : dep->package;
-
- printf("\"%s\" -> \"%s\" [fontname=Sans fontsize=8 color=gray]\n", pkg->id, dep_id);
- }
-}
-
-static bool
-apply_digraph(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
-{
- int eflag;
- pkgconf_list_t *list = data;
- pkgconf_pkg_t *last_seen = NULL;
- pkgconf_node_t *iter;
-
- printf("digraph deptree {\n");
- printf("edge [color=blue len=7.5 fontname=Sans fontsize=8]\n");
- printf("node [fontname=Sans fontsize=8]\n");
- printf("\"user:request\" [fontname=Sans fontsize=8]\n");
-
- PKGCONF_FOREACH_LIST_ENTRY(list->head, iter)
- {
- pkgconf_queue_t *pkgq = iter->data;
- pkgconf_pkg_t *pkg = pkgconf_pkg_find(client, pkgq->package);
- printf("\"user:request\" -> \"%s\" [fontname=Sans fontsize=8]\n", pkg == NULL ? pkgq->package : pkg->id);
- if (pkg != NULL)
- pkgconf_pkg_unref(client, pkg);
- }
-
- eflag = pkgconf_pkg_traverse(client, world, print_digraph_node, &last_seen, maxdepth, 0);
-
- if (eflag != PKGCONF_PKG_ERRF_OK)
- return false;
-
- printf("}\n");
- return true;
-}
-
-static void
-print_solution_node(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *unused)
-{
- (void) client;
- (void) unused;
-
- printf("%s (%"PRIu64")%s\n", pkg->id, pkg->identifier, (pkg->flags & PKGCONF_PKG_PROPF_VISITED_PRIVATE) == PKGCONF_PKG_PROPF_VISITED_PRIVATE ? " [private]" : "");
-}
-
-static bool
-apply_print_solution(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
-{
- int eflag;
-
- eflag = pkgconf_pkg_traverse(client, world, print_solution_node, unused, maxdepth, 0);
-
- return eflag == PKGCONF_PKG_ERRF_OK;
-}
-#endif
-
-static bool
-apply_modversion(pkgconf_client_t *client, pkgconf_pkg_t *world, void *data, int maxdepth)
-{
- pkgconf_node_t *queue_iter;
- pkgconf_list_t *pkgq = data;
- (void) client;
- (void) maxdepth;
-
- PKGCONF_FOREACH_LIST_ENTRY(pkgq->head, queue_iter)
- {
- pkgconf_node_t *world_iter;
- pkgconf_queue_t *queue_node = queue_iter->data;
-
- PKGCONF_FOREACH_LIST_ENTRY(world->required.head, world_iter)
- {
- pkgconf_dependency_t *dep = world_iter->data;
- pkgconf_pkg_t *pkg = dep->match;
-
- const size_t name_len = strlen(pkg->why);
- if (name_len > strlen(queue_node->package) ||
- strncmp(pkg->why, queue_node->package, name_len) ||
- (queue_node->package[name_len] != 0 &&
- !isspace((unsigned char)queue_node->package[name_len]) &&
- !PKGCONF_IS_OPERATOR_CHAR(queue_node->package[name_len])))
- continue;
-
- if (pkg->version != NULL) {
- if (verbosity)
- printf("%s: ", pkg->id);
-
- printf("%s\n", pkg->version);
- }
-
- break;
- }
- }
-
- return true;
-}
-
-static bool
-apply_variables(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
-{
- pkgconf_node_t *iter;
- (void) client;
- (void) unused;
- (void) maxdepth;
-
- PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
- {
- pkgconf_dependency_t *dep = iter->data;
- pkgconf_pkg_t *pkg = dep->match;
-
- print_variables(pkg);
- }
-
- return true;
-}
-
-static bool
-apply_path(pkgconf_client_t *client, pkgconf_pkg_t *world, void *unused, int maxdepth)
-{
- pkgconf_node_t *iter;
- (void) client;
- (void) unused;
- (void) maxdepth;
-
- PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
- {
- pkgconf_dependency_t *dep = iter->data;
- pkgconf_pkg_t *pkg = dep->match;
-
- /* a module entry with no filename is either virtual, static (builtin) or synthesized. */
- if (pkg->filename != NULL)
- printf("%s\n", pkg->filename);
- }
-
- return true;
-}
-
-static bool
-apply_variable(pkgconf_client_t *client, pkgconf_pkg_t *world, void *variable, int maxdepth)
-{
- pkgconf_node_t *iter;
- (void) maxdepth;
-
- PKGCONF_FOREACH_LIST_ENTRY(world->required.head, iter)
- {
- pkgconf_dependency_t *dep = iter->data;
- pkgconf_pkg_t *pkg = dep->match;
- const char *var;
-
- var = pkgconf_tuple_find(client, &pkg->vars, variable);
-
- if (var != NULL)
- printf("%s%s", iter->prev != NULL ? " " : "", var);
- }
-
- printf("\n");
-
- return true;
-}
-
-static bool
-apply_env_var(const char *prefix, pkgconf_client_t *client, pkgconf_pkg_t *world, int maxdepth,
- unsigned int (*collect_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *list, int maxdepth),
- bool (*filter_fn)(const pkgconf_client_t *client, const pkgconf_fragment_t *frag, void *data),
- void (*postprocess_fn)(pkgconf_client_t *client, pkgconf_pkg_t *world, pkgconf_list_t *fragment_list))
-{
- pkgconf_list_t unfiltered_list = PKGCONF_LIST_INITIALIZER;
- pkgconf_list_t filtered_list = PKGCONF_LIST_INITIALIZER;
- unsigned int eflag;
- char *render_buf;
-
- eflag = collect_fn(client, world, &unfiltered_list, maxdepth);
- if (eflag != PKGCONF_PKG_ERRF_OK)
- return false;
-
- pkgconf_fragment_filter(client, &filtered_list, &unfiltered_list, filter_fn, NULL);
-
- if (postprocess_fn != NULL)
- postprocess_fn(client, world, &filtered_list);
-
- if (filtered_list.head == NULL)
- goto out;
-
- render_buf = pkgconf_fragment_render(&filtered_list, true, want_render_ops);
- printf("%s='%s'\n", prefix, render_buf);
- free(render_buf);
-
-out:
- pkgconf_fragment_free(&unfiltered_list);
- pkgconf_fragment_free(&filtered_list);
+ if (state->error_msgout == NULL)
+ return true;
+ pkgconf_output_file_fmt(state->error_msgout, "%s", msg);
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)
+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;
-
- print_requires_private(pkg);
+ pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
}
- 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)
+relocate_path(const char *path)
{
- int eflag;
+ pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER;
- eflag = pkgconf_pkg_traverse(client, world, check_uninstalled, data, maxdepth, 0);
+ pkgconf_buffer_append(&pathbuf, path);
+ pkgconf_path_relocate(&pathbuf);
- if (eflag != PKGCONF_PKG_ERRF_OK)
- return false;
+ printf("%s\n", pkgconf_buffer_str(&pathbuf));
- return true;
+ pkgconf_buffer_finalize(&pathbuf);
}
#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)
@@ -931,6 +209,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);
@@ -939,6 +218,8 @@ usage(void)
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(" --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");
printf("\nfiltering output:\n\n");
@@ -948,162 +229,44 @@ usage(void)
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");
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)
+int
+main(int argc, char *argv[])
{
- 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)
+#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)
{
- pkgconf_path_t *pn = n->data;
- printf("%s ", pn->path);
+ original_console_cp = GetConsoleCP();
+ original_console_out_cp = GetConsoleOutputCP();
+ SetConsoleCP(CP_UTF8);
+ SetConsoleOutputCP(CP_UTF8);
+ atexit(restore_console_code_page);
}
-
- printf("\n");
-}
-
-static pkgconf_cross_personality_t *
-deduce_personality(char *argv[])
-{
- const char *argv0 = argv[0];
- char *i, *prefix;
- pkgconf_cross_personality_t *out;
-
- i = strrchr(argv0, '/');
- if (i != NULL)
- argv0 = i + 1;
-
-#if defined(_WIN32) || defined(_WIN64)
- i = strrchr(argv0, '\\');
- if (i != NULL)
- argv0 = i + 1;
-#endif
-
- i = strstr(argv0, "-pkg");
- if (i == NULL)
- return pkgconf_cross_personality_default();
-
- prefix = pkgconf_strndup(argv0, i - argv0);
- out = pkgconf_cross_personality_find(prefix);
- free(prefix);
- if (out == NULL)
- return pkgconf_cross_personality_default();
-
- return out;
-}
#endif
-static void
-unveil_handler(const pkgconf_client_t *client, const char *path, const char *permissions)
-{
- (void) client;
-
- if (pkgconf_unveil(path, permissions) == -1)
- {
- fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
-}
-
-static bool
-unveil_search_paths(const pkgconf_client_t *client, const pkgconf_cross_personality_t *personality)
-{
- pkgconf_node_t *n;
-
- if (pkgconf_unveil("/dev/null", "rwc") == -1)
- return false;
-
- PKGCONF_FOREACH_LIST_ENTRY(client->dir_list.head, n)
- {
- pkgconf_path_t *pn = n->data;
-
- if (pkgconf_unveil(pn->path, "r") == -1)
- return false;
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(personality->dir_list.head, n)
- {
- pkgconf_path_t *pn = n->data;
-
- if (pkgconf_unveil(pn->path, "r") == -1)
- return false;
- }
-
- pkgconf_client_set_unveil_handler(&pkg_client, unveil_handler);
-
- return true;
-}
-
-int
-main(int argc, char *argv[])
-{
int ret;
- pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
+ pkgconf_cli_state_t state = {
+ .want_flags = 0,
+ };
pkgconf_list_t dir_list = PKGCONF_LIST_INITIALIZER;
- char *builddir;
- char *sysroot_dir;
char *env_traverse_depth;
- char *required_pkgconfig_version = NULL;
- char *required_exact_module_version = NULL;
- char *required_max_module_version = NULL;
- char *required_module_version = NULL;
char *logfile_arg = NULL;
- char *want_env_prefix = NULL;
- unsigned int want_client_flags = PKGCONF_PKG_PKGF_NONE;
pkgconf_cross_personality_t *personality = NULL;
- bool opened_error_msgout = false;
- pkgconf_pkg_t world = {
- .id = "virtual:world",
- .realname = "virtual world package",
- .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
- };
if (pkgconf_pledge("stdio rpath wpath cpath unveil", NULL) == -1)
{
- fprintf(stderr, "pkgconf: pledge failed: %s\n", strerror(errno));
+ 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
@@ -1115,85 +278,92 @@ 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, },
+ { "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, &want_flags, PKG_DIGRAPH, },
- { "solution", no_argument, &want_flags, PKG_SOLUTION, },
+ { "digraph", no_argument, &state.want_flags, PKG_DIGRAPH, },
+ { "solution", no_argument, &state.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, },
+ { "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, },
+ { "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, &want_flags, PKG_SIMULATE, },
+ { "simulate", no_argument, &state.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 },
+ { "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 },
+ { "define-prefix", no_argument, &state.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 },
+ { "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
@@ -1202,25 +372,25 @@ 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);
+ state.maximum_traverse_depth = atoi(pkg_optarg);
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;
@@ -1229,16 +399,16 @@ 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:
@@ -1246,7 +416,7 @@ main(int argc, char *argv[])
break;
#endif
case 55:
- verbosity++;
+ state.verbosity++;
break;
case '?':
case ':':
@@ -1265,55 +435,41 @@ main(int argc, char *argv[])
#endif
}
-#ifndef PKGCONF_LITE
- if ((want_flags & PKG_DUMP_PERSONALITY) == PKG_DUMP_PERSONALITY)
- {
- dump_personality(personality);
- return EXIT_SUCCESS;
- }
-#endif
-
/* now, bring up the client. settings are preserved since the client is prealloced */
- pkgconf_client_init(&pkg_client, error_handler, NULL, personality);
-
- /* unveil the entire search path now that we have loaded the personality data. */
- if (!unveil_search_paths(&pkg_client, personality))
- {
- fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
- return EXIT_FAILURE;
- }
+ pkgconf_client_init(&state.pkg_client, error_handler, &state, personality, &state, environ_lookup_handler);
+ pkgconf_client_set_unveil_handler(&state.pkg_client, unveil_handler);
#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);
+ state.maximum_traverse_depth = atoi(env_traverse_depth);
- 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
- pkgconf_path_prepend_list(&pkg_client.dir_list, &dir_list);
+ pkgconf_path_prepend_list(&state.pkg_client.dir_list, &dir_list);
pkgconf_path_free(&dir_list);
- if ((want_flags & PKG_ABOUT) == PKG_ABOUT)
+ if ((state.want_flags & PKG_ABOUT) == PKG_ABOUT)
{
about();
@@ -1321,7 +477,7 @@ main(int argc, char *argv[])
goto out;
}
- if ((want_flags & PKG_VERSION) == PKG_VERSION)
+ if ((state.want_flags & PKG_VERSION) == PKG_VERSION)
{
version();
@@ -1329,7 +485,7 @@ main(int argc, char *argv[])
goto out;
}
- if ((want_flags & PKG_HELP) == PKG_HELP)
+ if ((state.want_flags & PKG_HELP) == PKG_HELP)
{
usage();
@@ -1337,148 +493,6 @@ 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);
-
- /* preload any files in PKG_CONFIG_PRELOADED_FILES */
- pkgconf_client_preload_from_environ(&pkg_client, "PKG_CONFIG_PRELOADED_FILES");
-
- if (required_pkgconfig_version != NULL)
- {
- if (pkgconf_compare_version(PACKAGE_VERSION, required_pkgconfig_version) >= 0)
- ret = EXIT_SUCCESS;
- else
- ret = EXIT_FAILURE;
-
- goto out;
- }
-
- if ((want_flags & PKG_LIST) == PKG_LIST)
- {
- pkgconf_scan_all(&pkg_client, NULL, print_list_entry);
- ret = EXIT_SUCCESS;
- goto out;
- }
-
- if ((want_flags & PKG_LIST_PACKAGE_NAMES) == PKG_LIST_PACKAGE_NAMES)
- {
- pkgconf_scan_all(&pkg_client, NULL, print_package_entry);
- ret = EXIT_SUCCESS;
- goto out;
- }
-
if (logfile_arg == NULL)
logfile_arg = getenv("PKG_CONFIG_LOG");
@@ -1486,338 +500,23 @@ main(int argc, char *argv[])
{
if (pkgconf_unveil(logfile_arg, "rwc") == -1)
{
- fprintf(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
+ pkgconf_output_file_fmt(stderr, "pkgconf: unveil failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
- logfile_out = fopen(logfile_arg, "w");
- pkgconf_audit_set_log(&pkg_client, logfile_out);
- }
-
- if (required_module_version != NULL)
- {
- pkgconf_pkg_t *pkg = NULL;
- pkgconf_node_t *node;
- pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER;
-
- while (argv[pkg_optind])
- {
- pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0);
- pkg_optind++;
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
- {
- pkgconf_dependency_t *pkgiter = node->data;
-
- pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package);
- if (pkg == NULL)
- {
- if (want_flags & PKG_PRINT_ERRORS)
- pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package);
-
- ret = EXIT_FAILURE;
- goto cleanup;
- }
-
- if (pkgconf_compare_version(pkg->version, required_module_version) >= 0)
- {
- ret = EXIT_SUCCESS;
- goto cleanup;
- }
- }
-
- ret = EXIT_FAILURE;
-cleanup:
- if (pkg != NULL)
- pkgconf_pkg_unref(&pkg_client, pkg);
- pkgconf_dependency_free(&deplist);
- goto out;
- }
- else if (required_exact_module_version != NULL)
- {
- pkgconf_pkg_t *pkg = NULL;
- pkgconf_node_t *node;
- pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER;
-
- while (argv[pkg_optind])
- {
- pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0);
- pkg_optind++;
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
- {
- pkgconf_dependency_t *pkgiter = node->data;
-
- pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package);
- if (pkg == NULL)
- {
- if (want_flags & PKG_PRINT_ERRORS)
- pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package);
-
- ret = EXIT_FAILURE;
- goto cleanup2;
- }
-
- if (pkgconf_compare_version(pkg->version, required_exact_module_version) == 0)
- {
- ret = EXIT_SUCCESS;
- goto cleanup2;
- }
- }
-
- ret = EXIT_FAILURE;
-cleanup2:
- if (pkg != NULL)
- pkgconf_pkg_unref(&pkg_client, pkg);
- pkgconf_dependency_free(&deplist);
- goto out;
- }
- else if (required_max_module_version != NULL)
- {
- pkgconf_pkg_t *pkg = NULL;
- pkgconf_node_t *node;
- pkgconf_list_t deplist = PKGCONF_LIST_INITIALIZER;
-
- while (argv[pkg_optind])
- {
- pkgconf_dependency_parse_str(&pkg_client, &deplist, argv[pkg_optind], 0);
- pkg_optind++;
- }
-
- PKGCONF_FOREACH_LIST_ENTRY(deplist.head, node)
- {
- pkgconf_dependency_t *pkgiter = node->data;
-
- pkg = pkgconf_pkg_find(&pkg_client, pkgiter->package);
- if (pkg == NULL)
- {
- if (want_flags & PKG_PRINT_ERRORS)
- pkgconf_error(&pkg_client, "Package '%s' was not found\n", pkgiter->package);
-
- ret = EXIT_FAILURE;
- goto cleanup3;
- }
-
- if (pkgconf_compare_version(pkg->version, required_max_module_version) <= 0)
- {
- ret = EXIT_SUCCESS;
- goto cleanup3;
- }
- }
-
- ret = EXIT_FAILURE;
-cleanup3:
- if (pkg != NULL)
- pkgconf_pkg_unref(&pkg_client, pkg);
- pkgconf_dependency_free(&deplist);
- goto out;
- }
-
- while (1)
- {
- char *package = argv[pkg_optind];
- char *end;
-
- if (package == NULL)
- break;
-
- /* check if there is a limit to the number of packages allowed to be included, if so and we have hit
- * the limit, stop adding packages to the queue.
- */
- if (maximum_package_count > 0 && pkgq.length >= maximum_package_count)
- break;
-
- while (isspace((unsigned char)package[0]))
- package++;
-
- /* skip empty packages */
- if (package[0] == '\0') {
- pkg_optind++;
- continue;
- }
-
- end = package + strlen(package) - 1;
- while(end > package && isspace((unsigned char)end[0])) end--;
- end[1] = '\0';
-
- if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1])))
- {
- pkgconf_queue_push(&pkgq, package);
- pkg_optind++;
- }
- else if (argv[pkg_optind + 2] == NULL)
- {
- char packagebuf[PKGCONF_BUFSIZE];
-
- snprintf(packagebuf, sizeof packagebuf, "%s %s", package, argv[pkg_optind + 1]);
- pkg_optind += 2;
-
- pkgconf_queue_push(&pkgq, packagebuf);
- }
- else
- {
- char packagebuf[PKGCONF_BUFSIZE];
-
- snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]);
- pkg_optind += 3;
-
- pkgconf_queue_push(&pkgq, packagebuf);
- }
- }
-
- if (pkgq.head == NULL)
- {
- fprintf(stderr, "Please specify at least one package name on the command line.\n");
- ret = EXIT_FAILURE;
- goto out;
- }
-
- ret = EXIT_SUCCESS;
-
- if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth))
- {
- ret = EXIT_FAILURE;
- goto out;
- }
-
- /* we shouldn't need to unveil any more filesystem accesses from this point, so lock it down */
- if (pkgconf_unveil(NULL, NULL) == -1)
- {
- fprintf(stderr, "pkgconf: unveil lockdown failed: %s\n", strerror(errno));
- return EXIT_FAILURE;
- }
-
-#ifndef PKGCONF_LITE
- if ((want_flags & PKG_SIMULATE) == PKG_SIMULATE)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
-
- pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ERRORS);
- apply_simulate(&pkg_client, &world, NULL, -1);
+ state.logfile_out = fopen(logfile_arg, "a");
+ pkgconf_audit_set_log(&state.pkg_client, state.logfile_out);
}
-#endif
-
- if ((want_flags & PKG_VALIDATE) == PKG_VALIDATE)
- goto out;
- if ((want_flags & PKG_DUMP_LICENSE) == PKG_DUMP_LICENSE)
- {
- apply_license(&pkg_client, &world, &ret, 2);
- goto out;
- }
-
- if ((want_flags & PKG_UNINSTALLED) == PKG_UNINSTALLED)
- {
- ret = EXIT_FAILURE;
- apply_uninstalled(&pkg_client, &world, &ret, 2);
- goto out;
- }
-
- if (want_env_prefix != NULL)
- {
- apply_env(&pkg_client, &world, want_env_prefix, 2);
- goto out;
- }
-
- if ((want_flags & PKG_PROVIDES) == PKG_PROVIDES)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_provides(&pkg_client, &world, NULL, 2);
- }
-
-#ifndef PKGCONF_LITE
- if ((want_flags & PKG_DIGRAPH) == PKG_DIGRAPH)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_digraph(&pkg_client, &world, &pkgq, 2);
- }
-
- if ((want_flags & PKG_SOLUTION) == PKG_SOLUTION)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_print_solution(&pkg_client, &world, NULL, 2);
- }
-#endif
-
- if ((want_flags & PKG_MODVERSION) == PKG_MODVERSION)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_modversion(&pkg_client, &world, &pkgq, 2);
- }
-
- if ((want_flags & PKG_PATH) == PKG_PATH)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
-
- pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL);
- apply_path(&pkg_client, &world, NULL, 2);
- }
-
- if ((want_flags & PKG_VARIABLES) == PKG_VARIABLES)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_variables(&pkg_client, &world, NULL, 2);
- }
-
- if (want_variable)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
-
- pkgconf_client_set_flags(&pkg_client, want_client_flags | PKGCONF_PKG_PKGF_SKIP_ROOT_VIRTUAL);
- apply_variable(&pkg_client, &world, want_variable, 2);
- }
-
- if ((want_flags & PKG_REQUIRES) == PKG_REQUIRES)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
- apply_requires(&pkg_client, &world, NULL, 2);
- }
-
- if ((want_flags & PKG_REQUIRES_PRIVATE) == PKG_REQUIRES_PRIVATE)
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
-
- apply_requires_private(&pkg_client, &world, NULL, 2);
- }
-
- if ((want_flags & PKG_FRAGMENT_TREE))
- {
- want_flags &= ~(PKG_CFLAGS|PKG_LIBS);
-
- apply_fragment_tree(&pkg_client, &world, NULL, 2);
- }
-
- if ((want_flags & PKG_CFLAGS))
- {
- apply_cflags(&pkg_client, &world, NULL, 2);
- }
-
- if ((want_flags & PKG_LIBS))
- {
- if (want_flags & PKG_CFLAGS)
- printf(" ");
-
- if (!(want_flags & PKG_STATIC))
- pkgconf_client_set_flags(&pkg_client, pkg_client.flags & ~PKGCONF_PKG_PKGF_SEARCH_PRIVATE);
+ 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;
}
diff --git a/cli/renderer-msvc.c b/cli/renderer-msvc.c
index 8e31699bbca4..07c9ccf7f5b0 100644
--- a/cli/renderer-msvc.c
+++ b/cli/renderer-msvc.c
@@ -2,6 +2,8 @@
* renderer-msvc.c
* MSVC library syntax renderer
*
+ * SPDX-License-Identifier: pkgconf
+ *
* Copyright (c) 2017 pkgconf authors (see AUTHORS).
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -20,17 +22,14 @@
#include "renderer-msvc.h"
static inline bool
-fragment_should_quote(const pkgconf_fragment_t *frag)
+should_quote(const pkgconf_buffer_t *buf)
{
const char *src;
- if (frag->data == NULL)
- return false;
-
- for (src = frag->data; *src; src++)
+ for (src = buf->base; *src && src < buf->end; src++)
{
if (((*src < ' ') ||
- (*src >= (' ' + (frag->children.head != NULL ? 1 : 0)) && *src < '$') ||
+ (*src >= (' ') && *src < '$') ||
(*src > '$' && *src < '(') ||
(*src > ')' && *src < '+') ||
(*src > ':' && *src < '=') ||
@@ -45,127 +44,54 @@ fragment_should_quote(const pkgconf_fragment_t *frag)
return false;
}
-static inline size_t
-fragment_len(const pkgconf_fragment_t *frag)
-{
- size_t len = 1;
-
- if (frag->type)
- len += 2;
-
- if (frag->data != NULL)
- {
- len += strlen(frag->data);
-
- if (fragment_should_quote(frag))
- len += 2;
- }
-
- return len;
-}
-
static inline bool
allowed_fragment(const pkgconf_fragment_t *frag)
{
return !(!frag->type || frag->data == NULL || strchr("DILl", frag->type) == NULL);
}
-static size_t
-msvc_renderer_render_len(const pkgconf_list_t *list, bool escape)
-{
- (void) escape;
-
- size_t out = 1; /* trailing nul */
- pkgconf_node_t *node;
-
- PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
- {
- const pkgconf_fragment_t *frag = node->data;
-
- if (!allowed_fragment(frag))
- continue;
-
- switch (frag->type)
- {
- case 'L':
- out += 9; /* "/libpath:" */
- break;
- case 'l':
- out += 4; /* ".lib" */
- break;
- default:
- break;
- }
-
- out += fragment_len(frag);
- }
-
- return out;
-}
-
static void
-msvc_renderer_render_buf(const pkgconf_list_t *list, char *buf, size_t buflen, bool escape)
+msvc_renderer_render(const pkgconf_fragment_render_ctx_t *ctx, const pkgconf_fragment_t *frag, pkgconf_buffer_t *buf)
{
- pkgconf_node_t *node;
- char *bptr = buf;
-
- memset(buf, 0, buflen);
-
- PKGCONF_FOREACH_LIST_ENTRY(list->head, node)
- {
- const pkgconf_fragment_t *frag = node->data;
- size_t buf_remaining = buflen - (bptr - buf);
- size_t cnt;
+ pkgconf_buffer_t tmpbuf = PKGCONF_BUFFER_INITIALIZER;
+ bool escape = ctx->escape;
- if (!allowed_fragment(frag))
- continue;
+ if (!allowed_fragment(frag))
+ return;
- if (fragment_len(frag) > buf_remaining)
- break;
-
- switch(frag->type) {
- case 'D':
- case 'I':
- *bptr++ = '/';
- *bptr++ = frag->type;
- break;
- case 'L':
- cnt = pkgconf_strlcpy(bptr, "/libpath:", buf_remaining);
- bptr += cnt;
- buf_remaining -= cnt;
- break;
- }
+ switch(frag->type) {
+ case 'D':
+ case 'I':
+ pkgconf_buffer_append_fmt(buf, "/%c", frag->type);
+ break;
+ case 'L':
+ pkgconf_buffer_append(buf, "/libpath:");
+ break;
+ }
- escape = fragment_should_quote(frag);
+ pkgconf_buffer_append(&tmpbuf, frag->data);
- if (escape)
- *bptr++ = '"';
+ if (frag->type == 'l')
+ pkgconf_buffer_append(&tmpbuf, ".lib");
- cnt = pkgconf_strlcpy(bptr, frag->data, buf_remaining);
- bptr += cnt;
- buf_remaining -= cnt;
+ escape = should_quote(&tmpbuf);
- if (frag->type == 'l')
- {
- cnt = pkgconf_strlcpy(bptr, ".lib", buf_remaining);
- bptr += cnt;
- }
+ if (escape)
+ pkgconf_buffer_push_byte(buf, '"');
- if (escape)
- *bptr++ = '"';
+ pkgconf_buffer_append(buf, pkgconf_buffer_str_or_empty(&tmpbuf));
- *bptr++ = ' ';
- }
+ if (escape)
+ pkgconf_buffer_push_byte(buf, '"');
- *bptr = '\0';
+ pkgconf_buffer_finalize(&tmpbuf);
}
-static const pkgconf_fragment_render_ops_t msvc_renderer_ops = {
- .render_len = msvc_renderer_render_len,
- .render_buf = msvc_renderer_render_buf
+static pkgconf_fragment_render_ops_t msvc_renderer_ops = {
+ .render = msvc_renderer_render
};
-const pkgconf_fragment_render_ops_t *
+pkgconf_fragment_render_ops_t *
msvc_renderer_get(void)
{
return &msvc_renderer_ops;
diff --git a/cli/renderer-msvc.h b/cli/renderer-msvc.h
index 2a095fdc36b4..6e9549902f54 100644
--- a/cli/renderer-msvc.h
+++ b/cli/renderer-msvc.h
@@ -2,6 +2,8 @@
* renderer-msvc.h
* MSVC library syntax renderer header
*
+ * SPDX-License-Identifier: pkgconf
+ *
* Copyright (c) 2017 pkgconf authors (see AUTHORS).
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -18,6 +20,6 @@
#include <libpkgconf/libpkgconf.h>
-const pkgconf_fragment_render_ops_t *msvc_renderer_get(void);
+pkgconf_fragment_render_ops_t *msvc_renderer_get(void);
#endif
diff --git a/cli/spdxtool/core.c b/cli/spdxtool/core.c
new file mode 100644
index 000000000000..5cf7f10e81e1
--- /dev/null
+++ b/cli/spdxtool/core.c
@@ -0,0 +1,729 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "util.h"
+#include "serialize.h"
+#include "core.h"
+#include "software.h"
+#include "simplelicensing.h"
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_core_agent_t *spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
+ *
+ * Create new /Core/Agent struct
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param char *creation_info_id: CreationInfo spdxId
+ * :param char *name: Name of agent
+ * :return: NULL if some problem occurs and Agent struct if not
+ */
+spdxtool_core_agent_t *
+spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_info_id, const char *name)
+{
+ if (!client || !creation_info_id || !name)
+ return NULL;
+
+ spdxtool_core_agent_t *agent = calloc(1, sizeof(spdxtool_core_agent_t));
+ if (!agent)
+ goto err;
+
+ agent->type = "Agent";
+
+ char *spdx_id_name = strdup(name);
+ if (!spdx_id_name)
+ goto err;
+
+ spdxtool_util_string_correction(spdx_id_name);
+
+ agent->spdx_id = spdxtool_util_get_spdx_id_string(client, agent->type, spdx_id_name);
+ free(spdx_id_name);
+
+ agent->creation_info = strdup(creation_info_id);
+ agent->name = strdup(name);
+
+ if (!agent->spdx_id || !agent->creation_info || !agent->name)
+ goto err;
+
+ return agent;
+
+err:
+ pkgconf_error(client, "spdxtool_core_agent_new: out of memory");
+ spdxtool_core_agent_free(agent);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
+ *
+ * Free /Core/Agent struct
+ *
+ * :param spdxtool_core_agent_t *agent: Agent struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_core_agent_free(spdxtool_core_agent_t *agent)
+{
+ if (!agent)
+ return;
+
+ free(agent->creation_info);
+ free(agent->spdx_id);
+ free(agent->name);
+
+ free(agent);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
+ *
+ * Serialize /Core/Agent struct to a JSON value tree.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const spdxtool_core_agent_t *agent: Agent struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the Agent object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", agent->type) &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", agent->creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", agent->spdx_id) &&
+ spdxtool_serialize_object_add_string(object_list, "name", agent->name)))
+ {
+ goto err;
+ }
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_core_agent_to_object: out of memory");
+
+ spdxtool_serialize_object_list_free(object_list);
+ return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time)
+ *
+ * Create new /Core/CreationInfo struct
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const char *agent_id: Agent spdxId
+ * :param const char *id: Id for creation info
+ * :param const char *time: If NULL current time is used if not then
+ * this time string is used. Time string should be
+ * in ISO8601 format: YYYY-MM-DDTHH:MM:SSZ
+ * :return: NULL if some problem occurs and CreationInfo struct if not
+ */
+spdxtool_core_creation_info_t *
+spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time)
+{
+ if (!client || !agent_id || !id)
+ return NULL;
+
+ spdxtool_core_creation_info_t *creation = calloc(1, sizeof(spdxtool_core_creation_info_t));
+ if (!creation)
+ goto err;
+
+ creation->type = "CreationInfo";
+ creation->created_using = "pkgconf spdxtool";
+ creation->id = strdup(id);
+ creation->created = time ? strdup(time) : spdxtool_util_get_current_iso8601_time();
+ creation->created_by = strdup(agent_id);
+ creation->spec_version = strdup(spdxtool_util_get_spdx_version(client));
+
+ if (!creation->id || !creation->created || !creation->created_by || !creation->spec_version)
+ goto err;
+
+ return creation;
+
+err:
+ pkgconf_error(client, "spdxtool_core_creation_info_new: out of memory");
+ spdxtool_core_creation_info_free(creation);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
+ *
+ * Free /Core/CreationInfo struct
+ *
+ * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation)
+{
+ if (!creation)
+ return;
+
+ free(creation->id);
+ free(creation->created);
+ free(creation->created_by);
+ free(creation->spec_version);
+
+ free(creation);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
+ *
+ * Serialize /Core/CreationInfo struct to a JSON value tree.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const spdxtool_core_creation_info_t *creation: CreationInfo struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the CreationInfo object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ spdxtool_serialize_array_t *created_by = spdxtool_serialize_array_new();
+ if (!created_by)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(created_by, creation->created_by))
+ {
+ spdxtool_serialize_array_free(created_by);
+ goto err;
+ }
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", creation->type) &&
+ spdxtool_serialize_object_add_string(object_list, "@id", creation->id) &&
+ spdxtool_serialize_object_add_string(object_list, "created", creation->created) &&
+ spdxtool_serialize_object_add_array(object_list, "createdBy", created_by) &&
+ spdxtool_serialize_object_add_string(object_list, "specVersion", creation->spec_version)))
+ {
+ goto err;
+ }
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_core_creation_info_to_object: out of memory");
+
+ spdxtool_serialize_object_list_free(object_list);
+ return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_core_creation_info_t *spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent)
+ *
+ * Create new /Core/SpdxDocument struct
+ * In SPDX Lite SBOM there can be only one SpdxDocument
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const char *spdx_id: Id of this SpdxDocument
+ * :param const char *creation_id: Id for creation info
+ * :param const char *agent_id: Agent for this document
+ * :return: NULL if some problem occurs and SpdxDocument struct if not
+ */
+spdxtool_core_spdx_document_t *
+spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id)
+{
+ if (!client || !spdx_id || !creation_id || !agent_id)
+ return NULL;
+
+ spdxtool_core_spdx_document_t *spdx = calloc(1, sizeof(spdxtool_core_spdx_document_t));
+ if (!spdx)
+ goto err;
+
+ spdx->type = "SpdxDocument";
+ spdx->spdx_id = strdup(spdx_id);
+ spdx->agent = strdup(agent_id);
+ spdx->creation_info = strdup(creation_id);
+
+ if (!spdx->spdx_id || !spdx->agent || !spdx->creation_info)
+ goto err;
+
+ return spdx;
+
+err:
+ pkgconf_error(client, "spdxtool_core_spdx_document_new: out of memory");
+ spdxtool_core_spdx_document_free(spdx);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
+ *
+ * Free /Core/SpdxDocument struct
+ *
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx)
+{
+ pkgconf_node_t *iter = NULL, *iter_next = NULL;
+
+ if (!spdx)
+ return;
+
+ free(spdx->spdx_id);
+ free(spdx->creation_info);
+ free(spdx->agent);
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->rootElement.head, iter_next, iter)
+ {
+ spdxtool_software_sbom_t *sbom = iter->data;
+ spdxtool_software_sbom_free(sbom);
+ free(iter);
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->element.head, iter_next, iter)
+ {
+ free(iter->data);
+ free(iter);
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->licenses.head, iter_next, iter)
+ {
+ spdxtool_simplelicensing_license_expression_t *expression = iter->data;
+ spdxtool_simplelicensing_licenseExpression_free(expression);
+ free(iter);
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->relationships.head, iter_next, iter)
+ {
+ spdxtool_core_relationship_t *relationship = iter->data;
+ spdxtool_core_relationship_free(relationship);
+ free(iter);
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(spdx->packages.head, iter_next, iter)
+ {
+ free(iter);
+ }
+
+ free(spdx);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
+ *
+ * Serialize /Core/SpdxDocument struct to a JSON value tree. This function
+ * should be called after all SBOMs and packages have been serialized so that
+ * the document's element and rootElement lists are fully populated.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the SpdxDocument object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = NULL;
+ spdxtool_serialize_array_t *root_element_array = NULL;
+ spdxtool_serialize_array_t *element_array = NULL;
+
+ object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ root_element_array = spdxtool_serialize_array_new();
+ if (!root_element_array)
+ goto err;
+
+ pkgconf_node_t *iter = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
+ {
+ spdxtool_software_sbom_t *sbom = iter->data;
+ if (!spdxtool_serialize_array_add_string(root_element_array, sbom->spdx_id))
+ goto err;
+ }
+
+ element_array = spdxtool_serialize_array_new();
+ if (!element_array)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(element_array, spdx->agent))
+ goto err;
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->element.head, iter)
+ {
+ char *element_id = iter->data;
+ if (!spdxtool_serialize_array_add_string(element_array, element_id))
+ goto err;
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
+ {
+ spdxtool_software_sbom_t *sbom = iter->data;
+ char *pkg_spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId");
+ if (!pkg_spdx_id)
+ goto err;
+
+ bool ok = spdxtool_serialize_array_add_string(element_array, sbom->spdx_id) &&
+ spdxtool_serialize_array_add_string(element_array, pkg_spdx_id);
+ free(pkg_spdx_id);
+
+ if (!ok)
+ goto err;
+ }
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", spdx->type) &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", spdx->creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", spdx->spdx_id) &&
+ spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array) &&
+ spdxtool_serialize_object_add_array(object_list, "element", element_array)))
+ {
+ goto err;
+ }
+
+ root_element_array = NULL;
+ element_array = NULL;
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_core_spdx_document_to_object: out of memory");
+
+ spdxtool_serialize_object_list_free(object_list);
+ spdxtool_serialize_array_free(root_element_array);
+ spdxtool_serialize_array_free(element_array);
+ return ret;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
+ *
+ * Find out if specific license is already there.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
+ * :param const char *license: SPDX name of license
+ * :return: true is license is there and false if not
+ */
+bool
+spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license)
+{
+ pkgconf_node_t *iter = NULL;
+ spdxtool_simplelicensing_license_expression_t *expression = NULL;
+
+ (void) client;
+
+ if (!license || !spdx)
+ {
+ return false;
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter)
+ {
+ expression = iter->data;
+ if (!strcmp(expression->license_expression, license))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
+ *
+ * Add license to SpdxDocument and make sure that specific license is not already there.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
+ * :param const char *license: SPDX name of license
+ * :return: true on success, false on failure
+ */
+bool
+spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license)
+{
+ if (!license || !spdx)
+ return false;
+
+ if (spdxtool_core_spdx_document_is_license(client, spdx, license))
+ return true;
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ {
+ pkgconf_error(client, "spdxtool_core_spdx_document_add_license: out of memory");
+ return false;
+ }
+
+ spdxtool_simplelicensing_license_expression_t *expression = spdxtool_simplelicensing_licenseExpression_new(client, license);
+ if (!expression)
+ {
+ free(node);
+ return false;
+ }
+
+ pkgconf_node_insert_tail(node, expression, &spdx->licenses);
+ if (!spdxtool_core_spdx_document_add_element(client, spdx, expression->spdx_id))
+ return false;
+
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
+ *
+ * Add element spdxId to SpdxDocument
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
+ * :param char *element: spdxId of element
+ * :return: true on success, false on failure
+ */
+bool
+spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element)
+{
+ if (!element || !spdx)
+ return false;
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ {
+ pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
+ return false;
+ }
+
+ char *nelement = strdup(element);
+ if (!nelement)
+ {
+ pkgconf_error(client, "spdxtool_core_spdx_document_add_element: out of memory");
+ free(node);
+ return false;
+ }
+
+ pkgconf_node_insert_tail(node, nelement, &spdx->element);
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
+ *
+ * Add relationship rel to SpdxDocument
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct being used.
+ * :param spdxtool_core_relationship_t *relationship: relationship to add.
+ * :return: true on success, false on failure
+ */
+bool
+spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship)
+{
+ if (!client || !spdx || !relationship)
+ return false;
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ {
+ pkgconf_error(client, "spdxtool_core_spdx_document_add_relationship: out of memory");
+ return false;
+ }
+
+ pkgconf_node_insert_tail(node, relationship, &spdx->relationships);
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: bool spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
+ *
+ * Register a package with the SpdxDocument for later serialization. The document
+ * does not take ownership of the package pointer; the package must outlive the
+ * document and will not be freed by spdxtool_core_spdx_document_free.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct to register the package with.
+ * :param pkgconf_pkg_t *pkg: Package to register. Ownership is NOT transferred.
+ * :return: true on success, false on failure
+ */
+bool
+spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg)
+{
+ if (!client || !spdx || !pkg)
+ return false;
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ {
+ pkgconf_error(client, "spdxtool_core_spdx_document_add_package: out of memory");
+ return false;
+ }
+
+ pkgconf_node_insert_tail(node, pkg, &spdx->packages);
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_core_relationship_t *spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, const char *to, const char *relationship_type)
+ *
+ * Create new /Core/Relationship struct
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const char *creation_id: Id for creation info
+ * :param const char *spdx_id: Id of this SpdxDocument
+ * :param const char *from: from spdxId
+ * :param const char *to: to spdxId
+ * :param const char *relationship_type: These can be found on SPDX documentation
+ * :return: NULL if some problem occurs and SpdxDocument struct if not
+ */
+spdxtool_core_relationship_t *
+spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type)
+{
+ if (!client || !creation_info_id || !spdx_id || !from || !to || !relationship_type)
+ return NULL;
+
+ spdxtool_core_relationship_t *relationship = calloc(1, sizeof(spdxtool_core_relationship_t));
+ if (!relationship)
+ goto err;
+
+ relationship->type = "Relationship";
+ relationship->creation_info = strdup(creation_info_id);
+ relationship->spdx_id = strdup(spdx_id);
+ relationship->from = strdup(from);
+ relationship->to = to;
+ relationship->relationship_type = strdup(relationship_type);
+
+ if (!relationship->creation_info || !relationship->spdx_id || !relationship->from || !relationship->relationship_type)
+ goto err;
+
+ return relationship;
+
+err:
+ pkgconf_error(client, "spdxtool_core_relationship_new: out of memory");
+ spdxtool_core_relationship_free(relationship);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
+ *
+ * Free /Core/Relationship struct
+ *
+ * :param spdxtool_core_relationship_t *relationship: Relationship struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship)
+{
+ if (!relationship)
+ return;
+
+ free(relationship->spdx_id);
+ free(relationship->creation_info);
+ free(relationship->from);
+ pkgconf_license_free(relationship->to);
+ free(relationship->to);
+ free(relationship->relationship_type);
+
+ free(relationship);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
+ *
+ * Serialize /Core/Relationship struct to a JSON value tree.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const spdxtool_core_relationship_t *relationship: Relationship struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the Relationship object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ spdxtool_serialize_array_t *to = spdxtool_serialize_array_new();
+ if (!to)
+ goto err;
+
+ pkgconf_node_t *node = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY(relationship->to->head, node)
+ {
+ const pkgconf_license_t *license = node->data;
+ if (!spdxtool_serialize_array_add_string(to, license->data))
+ {
+ spdxtool_serialize_array_free(to);
+ goto err;
+ }
+ }
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", relationship->type) &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", relationship->creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", relationship->spdx_id) &&
+ spdxtool_serialize_object_add_string(object_list, "from", relationship->from) &&
+ spdxtool_serialize_object_add_array(object_list, "to", to) &&
+ spdxtool_serialize_object_add_string(object_list, "relationshipType", relationship->relationship_type)))
+ {
+ goto err;
+ }
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_core_relationship_to_object: out of memory");
+
+ spdxtool_serialize_object_list_free(object_list);
+ return ret;
+}
diff --git a/cli/spdxtool/core.h b/cli/spdxtool/core.h
new file mode 100644
index 000000000000..0cbb1f4b3d51
--- /dev/null
+++ b/cli/spdxtool/core.h
@@ -0,0 +1,77 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#ifndef CLI__SPDXTOOL__CORE_H
+#define CLI__SPDXTOOL__CORE_H
+
+#include <stdlib.h>
+#include "util.h"
+#include "serialize.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+spdxtool_core_agent_t *
+spdxtool_core_agent_new(pkgconf_client_t *client, const char *creation_id, const char *name);
+
+void
+spdxtool_core_agent_free(spdxtool_core_agent_t *agent);
+
+spdxtool_serialize_value_t *
+spdxtool_core_agent_to_object(pkgconf_client_t *client, const spdxtool_core_agent_t *agent);
+
+spdxtool_core_creation_info_t *
+spdxtool_core_creation_info_new(pkgconf_client_t *client, const char *agent_id, const char *id, const char *time);
+
+void
+spdxtool_core_creation_info_free(spdxtool_core_creation_info_t *creation);
+
+spdxtool_serialize_value_t *
+spdxtool_core_creation_info_to_object(pkgconf_client_t *client, const spdxtool_core_creation_info_t *creation);
+
+spdxtool_core_spdx_document_t *
+spdxtool_core_spdx_document_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *agent_id);
+
+bool
+spdxtool_core_spdx_document_is_license(pkgconf_client_t *client, const spdxtool_core_spdx_document_t *spdx, const char *license);
+
+bool
+spdxtool_core_spdx_document_add_license(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *license);
+
+bool
+spdxtool_core_spdx_document_add_relationship(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, spdxtool_core_relationship_t *relationship);
+
+bool
+spdxtool_core_spdx_document_add_package(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, pkgconf_pkg_t *pkg);
+
+bool
+spdxtool_core_spdx_document_add_element(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx, const char *element);
+
+void
+spdxtool_core_spdx_document_free(spdxtool_core_spdx_document_t *spdx);
+
+spdxtool_serialize_value_t *
+spdxtool_core_spdx_document_to_object(pkgconf_client_t *client, spdxtool_core_spdx_document_t *spdx);
+
+spdxtool_core_relationship_t *
+spdxtool_core_relationship_new(pkgconf_client_t *client, const char *creation_info_id, const char *spdx_id, const char *from, pkgconf_list_t *to, const char *relationship_type);
+
+void
+spdxtool_core_relationship_free(spdxtool_core_relationship_t *relationship);
+
+spdxtool_serialize_value_t *
+spdxtool_core_relationship_to_object(pkgconf_client_t *client, const spdxtool_core_relationship_t *relationship);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/cli/spdxtool/main.c b/cli/spdxtool/main.c
new file mode 100644
index 000000000000..70cdce2dd891
--- /dev/null
+++ b/cli/spdxtool/main.c
@@ -0,0 +1,425 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include "libpkgconf/config.h"
+#include <libpkgconf/stdinc.h>
+#include <libpkgconf/libpkgconf.h>
+#include "getopt_long.h"
+#include "util.h"
+#include "core.h"
+#include "software.h"
+#include "serialize.h"
+#include "simplelicensing.h"
+
+#define PKG_VERSION (((uint64_t) 1) << 1)
+#define PKG_ABOUT (((uint64_t) 1) << 2)
+#define PKG_HELP (((uint64_t) 1) << 3)
+
+static const char *spdx_version = "3.0.1";
+static const char *bom_license = "CC0-1.0";
+static const char *xsd_any_url_default_base = "https://github.com/pkgconf/pkgconf";
+static const char *xsd_any_uri_default_base = "github.com:pkgconf:pkgconf";
+static int maximum_traverse_depth = 2000;
+
+static pkgconf_client_t pkg_client;
+static uint64_t want_flags;
+static size_t maximum_package_count = 0;
+// static int maximum_traverse_depth = 2000;
+static FILE *error_msgout = NULL;
+static FILE *sbom_out = NULL;
+
+static const char *
+environ_lookup_handler(const pkgconf_client_t *client, const char *key)
+{
+ (void) client;
+
+ return getenv(key);
+}
+
+static bool
+error_handler(const char *msg, const pkgconf_client_t *client, void *data)
+{
+ (void) client;
+ (void) data;
+ if (!pkgconf_output_file_fmt(error_msgout, "%s", msg))
+ {
+ pkgconf_error(client, "spdxtool: Could not output error message: %s", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+// NOTE: this function is passed to pkgconf_pkg_traverse
+static void
+generate_spdx_package(pkgconf_client_t *client, pkgconf_pkg_t *pkg, void *ptr)
+{
+ spdxtool_core_spdx_document_t *document = (spdxtool_core_spdx_document_t *)ptr;
+ pkgconf_node_t *node = NULL;
+ spdxtool_software_sbom_t *sbom = NULL;
+ char *package_spdx = NULL;
+ char *spdx_id_string = NULL;
+ char sep = spdxtool_util_get_uri_separator(client);
+
+ if (pkg->flags & PKGCONF_PKG_PROPF_VIRTUAL)
+ return;
+
+ spdx_id_string = spdxtool_util_get_spdx_id_string(client, "software_Sbom", pkg->id);
+ if (!spdx_id_string)
+ goto err;
+
+ sbom = spdxtool_software_sbom_new(client, spdx_id_string, document->creation_info, "build");
+ free(spdx_id_string);
+ spdx_id_string = NULL;
+ if (!sbom)
+ goto err;
+
+ sbom->spdx_document = document;
+ sbom->rootElement = pkg;
+
+ package_spdx = spdxtool_util_get_spdx_id_string(client, "Package", pkg->id);
+ if (!package_spdx)
+ goto err;
+
+ pkgconf_tuple_add(client, &pkg->vars, "spdxId", package_spdx, false, 0);
+ free(package_spdx);
+ package_spdx = NULL;
+
+ pkgconf_tuple_add(client, &pkg->vars, "creationInfo", document->creation_info, false, 0);
+ pkgconf_tuple_add(client, &pkg->vars, "agent", document->agent, false, 0);
+
+ if (pkg->license.head != NULL)
+ {
+ pkgconf_buffer_t spdx_id_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append_fmt(&spdx_id_buf, "%s%chasDeclaredLicense", pkg->id, sep);
+ char *spdx_id_name = pkgconf_buffer_freeze(&spdx_id_buf);
+ if (!spdx_id_name)
+ goto err;
+
+ package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name);
+ free(spdx_id_name);
+ if (!package_spdx)
+ goto err;
+
+ pkgconf_tuple_add(client, &pkg->vars, "hasDeclaredLicense", package_spdx, false, 0);
+ free(package_spdx);
+ package_spdx = NULL;
+
+ pkgconf_buffer_t concluded_buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_buffer_append_fmt(&concluded_buf, "%s%chasConcludedLicense", pkg->id, sep);
+ spdx_id_name = pkgconf_buffer_freeze(&concluded_buf);
+ if (!spdx_id_name)
+ goto err;
+
+ package_spdx = spdxtool_util_get_spdx_id_string(client, "Relationship", spdx_id_name);
+ free(spdx_id_name);
+ if (!package_spdx)
+ goto err;
+
+ pkgconf_tuple_add(client, &pkg->vars, "hasConcludedLicense", package_spdx, false, 0);
+ free(package_spdx);
+ package_spdx = NULL;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node)
+ {
+ const pkgconf_license_t *license = node->data;
+ if (license->type == PKGCONF_LICENSE_EXPRESSION)
+ {
+ if (!spdxtool_core_spdx_document_add_license(client, document, license->data))
+ goto err;
+ }
+ }
+ }
+
+ node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ goto err;
+
+ pkgconf_node_insert_tail(node, sbom, &document->rootElement);
+ return;
+
+err:
+ pkgconf_error(client, "generate_spdx_package: failed for %s", pkg->id);
+ free(package_spdx);
+ free(spdx_id_string);
+ spdxtool_software_sbom_free(sbom);
+}
+
+static bool
+generate_spdx(pkgconf_client_t *client, pkgconf_pkg_t *world, const char *creation_time, const char *creation_id, const char *agent_name)
+{
+ const char *agent_name_string = agent_name ? agent_name : "Default";
+ const char *creation_id_string = creation_id ? creation_id : "_:creationinfo_1";
+
+ spdxtool_core_agent_t *agent = spdxtool_core_agent_new(client, creation_id_string, agent_name_string);
+ if (!agent)
+ {
+ pkgconf_error(client, "Could not create agent struct");
+ return false;
+ }
+
+ spdxtool_core_creation_info_t *creation = spdxtool_core_creation_info_new(client, agent->spdx_id, creation_id_string, creation_time);
+ if (!creation)
+ {
+ pkgconf_error(client, "Could not create creation info struct");
+ spdxtool_core_agent_free(agent);
+ return false;
+ }
+
+ char *spdx_id_int = spdxtool_util_get_spdx_id_int(client, "spdxDocument");
+ spdxtool_core_spdx_document_t *document = spdxtool_core_spdx_document_new(client, spdx_id_int, creation_id_string, agent->spdx_id);
+ free(spdx_id_int);
+ if (!document)
+ {
+ pkgconf_error(client, "Could not create document");
+ spdxtool_core_creation_info_free(creation);
+ spdxtool_core_agent_free(agent);
+ return false;
+ }
+
+ int eflag = pkgconf_pkg_traverse(client, world, generate_spdx_package, document, maximum_traverse_depth, 0);
+ if (eflag != PKGCONF_PKG_ERRF_OK)
+ {
+ spdxtool_core_spdx_document_free(document);
+ spdxtool_core_creation_info_free(creation);
+ spdxtool_core_agent_free(agent);
+ return false;
+ }
+
+ spdxtool_serialize_value_t *root = spdxtool_serialize_sbom(client, agent, creation, document);
+ if (!root)
+ {
+ spdxtool_core_spdx_document_free(document);
+ spdxtool_core_creation_info_free(creation);
+ spdxtool_core_agent_free(agent);
+ return false;
+ }
+
+ pkgconf_buffer_t buffer = PKGCONF_BUFFER_INITIALIZER;
+ spdxtool_serialize_value_to_buf(&buffer, root, 0);
+ spdxtool_serialize_value_free(root);
+
+ bool ret = pkgconf_output_file_fmt(sbom_out, "%s\n", pkgconf_buffer_str(&buffer));
+ pkgconf_buffer_finalize(&buffer);
+
+ spdxtool_core_spdx_document_free(document);
+ spdxtool_core_creation_info_free(creation);
+ spdxtool_core_agent_free(agent);
+
+ if (!ret)
+ pkgconf_error(client, "spdxtool: Could not output to file: %s", strerror(errno));
+ return ret;
+}
+
+static int
+version(void)
+{
+ printf("spdxtool %s\n", PACKAGE_VERSION);
+ return EXIT_SUCCESS;
+}
+
+static int
+about(void)
+{
+ printf("spdxtool (%s %s)\n\n", PACKAGE_NAME, PACKAGE_VERSION);
+ printf("SPDX-License-Identifier: BSD-2-Clause\n\n");
+ printf("Copyright (c) 2025 The FreeBSD Foundation\n\n");
+ printf("Portions of this software were developed by\n");
+ printf("Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from\n");
+ printf("the FreeBSD Foundation\n\n");
+ printf("Report bugs at <%s>.\n", PACKAGE_BUGREPORT);
+ return EXIT_SUCCESS;
+}
+
+static int
+usage(void)
+{
+ printf("usage: spdxtool [modules]\n");
+
+ printf("\nOptions:\n");
+
+ printf(" --agent-name Set agent name [default: 'Default']\n");
+ printf(" --creation-time Use string as creation time (Should be in ISO8601 format) [default: current time]\n");
+ printf(" --creation-id Use string as creation id [default: '_:creationinfo_1']\n");
+ printf(" --help this message\n");
+ printf(" --about print bomtool version and license to stdout\n");
+ printf(" --version print bomtool version to stdout\n");
+ printf(" --output FILE output SBOM data to file\n");
+ printf(" --spdx-base-id URL Uset string as base of SPDX ids [default: %s]\n", xsd_any_uri_default_base);
+ printf(" --use-uri Use URIs not URLs as SPDX id");
+ printf(" --define-variable=varname=value define variable global 'varname' as 'value'\n");
+
+ return EXIT_SUCCESS;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret = EXIT_SUCCESS;
+ pkgconf_list_t pkgq = PKGCONF_LIST_INITIALIZER;
+ unsigned int want_client_flags = PKGCONF_PKG_PKGF_SEARCH_PRIVATE;
+ pkgconf_cross_personality_t *personality = pkgconf_cross_personality_default();
+ char *creation_time = NULL;
+ char *creation_id = NULL;
+ char *agent_name = NULL;
+ char world_id[] = "virtual:world";
+ char world_realname[] = "virtual world package";
+ const char *spdx_id_base = xsd_any_url_default_base;
+ bool colon_sep = false;
+ pkgconf_pkg_t world =
+ {
+ .id = world_id,
+ .realname = world_realname,
+ .flags = PKGCONF_PKG_PROPF_STATIC | PKGCONF_PKG_PROPF_VIRTUAL,
+ };
+
+ error_msgout = stderr;
+ sbom_out = stdout;
+
+ struct pkg_option options[] =
+ {
+ { "agent-name", required_argument, NULL, 100, },
+ { "creation-time", required_argument, NULL, 101, },
+ { "creation-id", required_argument, NULL, 102, },
+ { "version", no_argument, &want_flags, PKG_VERSION, },
+ { "about", no_argument, &want_flags, PKG_ABOUT, },
+ { "help", no_argument, &want_flags, PKG_HELP, },
+ { "output", required_argument, NULL, 103, },
+ { "spdx-base-id", required_argument, NULL, 104, },
+ { "use-uri", no_argument, NULL, 105, },
+ { "define-variable", required_argument, NULL, 106, },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1)
+ {
+ switch (ret)
+ {
+ case 100:
+ agent_name = pkg_optarg;
+ break;
+ case 101:
+ creation_time = pkg_optarg;
+ break;
+ case 102:
+ creation_id = pkg_optarg;
+ break;
+ case 103:
+ sbom_out = fopen(pkg_optarg, "w");
+ if (sbom_out == NULL)
+ {
+ pkgconf_output_file_fmt(stderr, "unable to open %s: %s\n", pkg_optarg, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ break;
+ case 104:
+ spdx_id_base = pkg_optarg;
+ break;
+ case 105:
+ // If SPDX id base have not been altered use default
+ if (!strcmp(spdx_id_base, xsd_any_url_default_base))
+ spdx_id_base = xsd_any_uri_default_base;
+ colon_sep = true;
+ break;
+ case 106:
+ pkgconf_tuple_define_global(&pkg_client, pkg_optarg);
+ break;
+ case '?':
+ case ':':
+ return EXIT_FAILURE;
+ default:
+ break;
+ }
+ }
+
+ pkgconf_client_init(&pkg_client, error_handler, NULL, personality, NULL, environ_lookup_handler);
+
+ /* we have determined what features we want most likely. in some cases, we override later. */
+ pkgconf_client_set_flags(&pkg_client, want_client_flags);
+
+ /* at this point, want_client_flags should be set, so build the dir list */
+ pkgconf_client_dir_list_build(&pkg_client, personality);
+
+
+ if ((want_flags & PKG_ABOUT) == PKG_ABOUT)
+ return about();
+
+ if ((want_flags & PKG_VERSION) == PKG_VERSION)
+ return version();
+
+ if ((want_flags & PKG_HELP) == PKG_HELP)
+ return usage();
+
+ while (1)
+ {
+ const char *package = argv[pkg_optind];
+
+ if (package == NULL)
+ break;
+
+ /* check if there is a limit to the number of packages allowed to be included, if so and we have hit
+ * the limit, stop adding packages to the queue.
+ */
+ if (maximum_package_count > 0 && pkgq.length > maximum_package_count)
+ break;
+
+ while (isspace((unsigned char)package[0]))
+ package++;
+
+ /* skip empty packages */
+ if (package[0] == '\0')
+ {
+ pkg_optind++;
+ continue;
+ }
+
+ if (argv[pkg_optind + 1] == NULL || !PKGCONF_IS_OPERATOR_CHAR(*(argv[pkg_optind + 1])))
+ {
+ pkgconf_queue_push(&pkgq, package);
+ pkg_optind++;
+ }
+ else
+ {
+ char packagebuf[PKGCONF_BUFSIZE];
+
+ snprintf(packagebuf, sizeof packagebuf, "%s %s %s", package, argv[pkg_optind + 1], argv[pkg_optind + 2]);
+ pkg_optind += 3;
+
+ pkgconf_queue_push(&pkgq, packagebuf);
+ }
+ }
+
+ if (!pkgconf_queue_solve(&pkg_client, &pkgq, &world, maximum_traverse_depth))
+ {
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ spdxtool_util_set_uri_root(&pkg_client, spdx_id_base);
+ spdxtool_util_set_uri_separator_colon(&pkg_client, colon_sep);
+ spdxtool_util_set_spdx_license(&pkg_client, bom_license);
+ spdxtool_util_set_spdx_version(&pkg_client, spdx_version);
+
+ if (!generate_spdx(&pkg_client, &world, creation_time, creation_id, agent_name))
+ {
+ ret = EXIT_FAILURE;
+ goto out;
+ }
+
+ ret = EXIT_SUCCESS;
+
+out:
+ pkgconf_solution_free(&pkg_client, &world);
+ pkgconf_queue_free(&pkgq);
+ pkgconf_cross_personality_deinit(personality);
+ pkgconf_client_deinit(&pkg_client);
+
+ return ret;
+}
diff --git a/cli/spdxtool/serialize.c b/cli/spdxtool/serialize.c
new file mode 100644
index 000000000000..69ef3a70a3b1
--- /dev/null
+++ b/cli/spdxtool/serialize.c
@@ -0,0 +1,467 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ *​
+ *​ Copyright (C) 2026 Elizabeth Ashford.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "util.h"
+#include "core.h"
+#include "software.h"
+#include "simplelicensing.h"
+#include "serialize.h"
+
+static void
+serialize_escape_string(pkgconf_buffer_t *buffer, const char *s)
+{
+ for (const char *p = s; *p; p++)
+ {
+ switch (*p)
+ {
+ case '\"':
+ pkgconf_buffer_append(buffer, "\\\"");
+ break;
+ case '\\':
+ pkgconf_buffer_append(buffer, "\\\\");
+ break;
+ case '\b':
+ pkgconf_buffer_append(buffer, "\\b");
+ break;
+ case '\f':
+ pkgconf_buffer_append(buffer, "\\f");
+ break;
+ case '\n':
+ pkgconf_buffer_append(buffer, "\\n");
+ break;
+ case '\r':
+ pkgconf_buffer_append(buffer, "\\r");
+ break;
+ case '\t':
+ pkgconf_buffer_append(buffer, "\\t");
+ break;
+ default:
+ if (*p < 0x20)
+ pkgconf_buffer_append_fmt(buffer, "\\u%04x", (unsigned int)*p);
+ else
+ pkgconf_buffer_push_byte(buffer, *p);
+ }
+ }
+}
+
+static inline void
+serialize_add_indent(pkgconf_buffer_t *buffer, unsigned int level)
+{
+ for (; level; level--)
+ pkgconf_buffer_append(buffer, " ");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent)
+ *
+ * Serialize the given JSON to the buffer
+ *
+ * :param pkgconf_buffer_t *buffer: Buffer to add to.
+ * :param spdxtool_serialize_value *value: Value to serialize.
+ * :param unsigned int indent: Indent level
+ * :return: true on success, false on failure
+ */
+bool
+spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent)
+{
+ if (!buffer || !value)
+ return false;
+
+ switch(value->type) {
+ case SPDXTOOL_SERIALIZE_TYPE_STRING:
+ pkgconf_buffer_push_byte(buffer, '"');
+ serialize_escape_string(buffer, value->value.s ? value->value.s : "");
+ pkgconf_buffer_push_byte(buffer, '"');
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_INT:
+ pkgconf_buffer_append_fmt(buffer, "%d", value->value.i);
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_BOOL:
+ pkgconf_buffer_append(buffer, value->value.b ? "true" : "false");
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_NULL:
+ pkgconf_buffer_append(buffer, "null");
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_OBJECT:
+ {
+ pkgconf_node_t *iter;
+ pkgconf_buffer_push_byte(buffer, '{');
+ pkgconf_buffer_push_byte(buffer, '\n');
+
+ PKGCONF_FOREACH_LIST_ENTRY(value->value.o->entries.head, iter)
+ {
+ spdxtool_serialize_object_t *entry = iter->data;
+ serialize_add_indent(buffer, indent + 1);
+ pkgconf_buffer_append_fmt(buffer, "\"%s\": ", entry->key);
+ spdxtool_serialize_value_to_buf(buffer, entry->value, indent + 1);
+ if (iter->next)
+ pkgconf_buffer_push_byte(buffer, ',');
+ pkgconf_buffer_push_byte(buffer, '\n');
+ }
+
+ serialize_add_indent(buffer, indent);
+ pkgconf_buffer_push_byte(buffer, '}');
+ break;
+ }
+ case SPDXTOOL_SERIALIZE_TYPE_ARRAY:
+ {
+ pkgconf_node_t *iter;
+ pkgconf_buffer_push_byte(buffer, '[');
+ pkgconf_buffer_push_byte(buffer, '\n');
+
+ PKGCONF_FOREACH_LIST_ENTRY(value->value.a->items.head, iter)
+ {
+ spdxtool_serialize_value_t *entry = iter->data;
+ serialize_add_indent(buffer, indent + 1);
+ spdxtool_serialize_value_to_buf(buffer, entry, indent + 1);
+ if (iter->next)
+ pkgconf_buffer_push_byte(buffer, ',');
+ pkgconf_buffer_push_byte(buffer, '\n');
+ }
+ serialize_add_indent(buffer, indent);
+ pkgconf_buffer_push_byte(buffer, ']');
+ break;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value)
+ *
+ * Add a key-value pair to a JSON object list. The key is copied internally.
+ * The object list takes ownership of the value.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string, copied internally.
+ * :param spdxtool_serialize_value_t *value: Value to associate with the key. Ownership transfers to the object list.
+ * :return: The value added, not owned by the caller.
+ */
+spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t *value)
+{
+ if (!object_list || !value)
+ {
+ spdxtool_serialize_value_free(value);
+ return NULL;
+ }
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ spdxtool_serialize_object_t *object = calloc(1, sizeof(spdxtool_serialize_object_t));
+ char *keycopy = key ? strdup(key) : strdup("");
+ if (!node || !object || !keycopy)
+ {
+ free(node);
+ free(keycopy);
+ spdxtool_serialize_object_free(object);
+ spdxtool_serialize_value_free(value);
+ return NULL;
+ }
+
+ object->key = keycopy;
+ object->value = value;
+ pkgconf_node_insert_tail(node, object, &object_list->entries);
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t value)
+ *
+ * Add a value to a JSON array. The array takes ownership of the value.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to add to.
+ * :param spdxtool_serialize_value_t value: Value to append. Ownership transfers to the array.
+ * :return: The value added, not owned by the caller.
+ */
+spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t *value)
+{
+ if (!array)
+ {
+ // Taking value, so free
+ spdxtool_serialize_value_free(value);
+ return NULL;
+ }
+
+ pkgconf_node_t *node = calloc(1, sizeof(pkgconf_node_t));
+ if (!node)
+ {
+ spdxtool_serialize_value_free(value);
+ return NULL;
+ }
+
+ pkgconf_node_insert_tail(node, value, &array->items);
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_object_list_t *spdxtool_serialize_object_list_new(void)
+ *
+ * Allocate and initialize a new empty JSON object list.
+ *
+ * :return: Pointer to a new spdxtool_serialize_object_list_t, or NULL on allocation failure.
+ */
+spdxtool_serialize_object_list_t *
+spdxtool_serialize_object_list_new(void)
+{
+ return calloc(1, sizeof(spdxtool_serialize_object_list_t));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_array_t *spdxtool_serialize_array_new(void)
+ *
+ * Allocate and initialize a new empty JSON array.
+ *
+ * :return: Pointer to a new spdxtool_serialize_array_t, or NULL on allocation failure.
+ */
+spdxtool_serialize_array_t *
+spdxtool_serialize_array_new(void)
+{
+ return calloc(1, sizeof(spdxtool_serialize_array_t));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_value_free(spdxtool_serialize_value_t *value)
+ *
+ * Free all resources owned by a JSON value. For strings, frees the string.
+ * For objects and arrays, recursively frees all children. The value pointer
+ * itself is not freed as it is assumed to be stack-allocated.
+ *
+ * :param spdxtool_serialize_value_t *value: Value to free. May be NULL.
+ * :return: nothing
+ */
+void
+spdxtool_serialize_value_free(spdxtool_serialize_value_t *value)
+{
+ if (!value)
+ return;
+
+ switch (value->type)
+ {
+ case SPDXTOOL_SERIALIZE_TYPE_STRING:
+ free(value->value.s);
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_ARRAY:
+ spdxtool_serialize_array_free(value->value.a);
+ break;
+ case SPDXTOOL_SERIALIZE_TYPE_OBJECT:
+ spdxtool_serialize_object_list_free(value->value.o);
+ break;
+ default:
+ // Nothing to do
+ break;
+ }
+
+ free(value);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_object_free(spdxtool_serialize_object_t *object)
+ *
+ * Free a JSON object entry, including its key string and owned value.
+ * The object pointer itself is not freed by this function.
+ *
+ * :param spdxtool_serialize_object_t *object: Object entry to free. May be NULL.
+ * :return: nothing
+ */
+void
+spdxtool_serialize_object_free(spdxtool_serialize_object_t *object)
+{
+ if (!object)
+ return;
+
+ free(object->key);
+ spdxtool_serialize_value_free(object->value);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list)
+ *
+ * Free a JSON object list and all of its entries, including their keys and values.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to free. May be NULL.
+ * :return: nothing
+ */
+void
+spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list)
+{
+ if (!object_list)
+ return;
+
+ pkgconf_node_t *iter_next = NULL, *iter = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(object_list->entries.head, iter_next, iter)
+ {
+ spdxtool_serialize_object_t *object = iter->data;
+ spdxtool_serialize_object_free(object);
+ free(object);
+ free(iter);
+ }
+
+ free(object_list);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_array_free(spdxtool_serialize_array_t *array)
+ *
+ * Free a JSON array and all of its elements.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to free. May be NULL.
+ * :return: nothing
+ */
+void
+spdxtool_serialize_array_free(spdxtool_serialize_array_t *array)
+{
+ if (!array)
+ return;
+
+ pkgconf_node_t *iter_next = NULL, *iter = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY_SAFE(array->items.head, iter_next, iter)
+ {
+ spdxtool_serialize_value_t *value = iter->data;
+ spdxtool_serialize_value_free(value);
+ free(iter);
+ }
+
+ free(array);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx)
+ *
+ * Serialize a complete SPDX SBOM document to a JSON-LD value tree. Iterates
+ * all SBOMs, packages, relationships, and license expressions registered on
+ * the document. The SpdxDocument object is emitted last to ensure all element
+ * IDs have been populated by prior iteration. This function must be called
+ * after pkgconf_pkg_traverse has completed so that all packages and their
+ * dependencies are registered on spdx.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_core_agent_t *agent: Agent struct to include in the document.
+ * :param spdxtool_core_creation_info_t *creation: CreationInfo struct to include in the document.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument struct containing all registered SBOMs, packages, relationships, and licenses.
+ * :return: spdxtool_serialize_value_t * representing the complete JSON-LD document, or a null string value on allocation failure.
+ */
+spdxtool_serialize_value_t *
+spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx)
+{
+ const char *errstr = "out of memory";
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_array_t *graph = NULL;
+ spdxtool_serialize_object_list_t *root = spdxtool_serialize_object_list_new();
+ if (!root)
+ goto err;
+
+ if (!spdxtool_serialize_object_add_string(root, "@context", "https://spdx.org/rdf/3.0.1/spdx-context.jsonld"))
+ goto err;
+
+ graph = spdxtool_serialize_array_new();
+ if (!graph)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_agent_to_object(client, agent)))
+ goto err;
+
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_creation_info_to_object(client, creation)))
+ goto err;
+
+ pkgconf_node_t *iter = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->licenses.head, iter)
+ {
+ spdxtool_simplelicensing_license_expression_t *expression = iter->data;
+ if (!expression)
+ {
+ errstr = "licenses list corrupted";
+ goto err;
+ }
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_simplelicensing_licenseExpression_to_object(client, spdx->creation_info, expression)))
+ goto err;
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->rootElement.head, iter)
+ {
+ spdxtool_software_sbom_t *current_sbom = iter->data;
+ if (!current_sbom)
+ {
+ errstr = "sbom list corrupted";
+ goto err;
+ }
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_sbom_to_object(client, current_sbom)))
+ goto err;
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->packages.head, iter)
+ {
+ pkgconf_pkg_t *pkg = iter->data;
+ if (!pkg)
+ {
+ errstr = "pkg list corrupted";
+ goto err;
+ }
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_software_package_to_object(client, pkg, spdx)))
+ goto err;
+ }
+
+ PKGCONF_FOREACH_LIST_ENTRY(spdx->relationships.head, iter)
+ {
+ spdxtool_core_relationship_t *relationship = iter->data;
+ if (!relationship)
+ {
+ errstr = "relationship list corrupted";
+ goto err;
+ }
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_relationship_to_object(client, relationship)))
+ goto err;
+ }
+
+ // SpdxDocument last — spdx->element must be fully populated first
+ if (!spdxtool_serialize_array_add_take(graph, spdxtool_core_spdx_document_to_object(client, spdx)))
+ goto err;
+
+ bool ok = spdxtool_serialize_object_add_array(root, "@graph", graph);
+ graph = NULL;
+ if (!ok)
+ goto err;
+
+ ret = spdxtool_serialize_value_object(root);
+ root = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_serialize_sbom: %s", errstr);
+
+ spdxtool_serialize_object_list_free(root);
+ spdxtool_serialize_array_free(graph);
+ return ret;
+}
diff --git a/cli/spdxtool/serialize.h b/cli/spdxtool/serialize.h
new file mode 100644
index 000000000000..e04b7f38b741
--- /dev/null
+++ b/cli/spdxtool/serialize.h
@@ -0,0 +1,539 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ *​
+ *​ Copyright (C) 2026 Elizabeth Ashford.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "util.h"
+
+#ifndef CLI__SPDXTOOL__SERIALIZE_H
+#define CLI__SPDXTOOL__SERIALIZE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum spdxtool_serialize_type_
+{
+ SPDXTOOL_SERIALIZE_TYPE_STRING, // JSON string type
+ SPDXTOOL_SERIALIZE_TYPE_INT, // JSON number type (int)
+ SPDXTOOL_SERIALIZE_TYPE_BOOL, // JSON bool type
+ SPDXTOOL_SERIALIZE_TYPE_NULL, // JSON null type
+ SPDXTOOL_SERIALIZE_TYPE_OBJECT, // JSON object type
+ SPDXTOOL_SERIALIZE_TYPE_ARRAY // JSON array type
+} spdxtool_serialize_type_t;
+
+typedef struct spdxtool_serialize_value_ {
+ spdxtool_serialize_type_t type;
+ union {
+ char *s;
+ int i;
+ bool b;
+ struct spdxtool_serialize_object_list_ *o;
+ struct spdxtool_serialize_array_ *a;
+ } value;
+} spdxtool_serialize_value_t;
+
+typedef struct spdxtool_serialize_object_ {
+ char *key;
+ spdxtool_serialize_value_t *value;
+} spdxtool_serialize_object_t;
+
+typedef struct spdxtool_serialize_object_list_ {
+ pkgconf_list_t entries;
+} spdxtool_serialize_object_list_t;
+
+typedef struct spdxtool_serialize_array_ {
+ pkgconf_list_t items;
+} spdxtool_serialize_array_t;
+
+bool
+spdxtool_serialize_value_to_buf(pkgconf_buffer_t *buffer, spdxtool_serialize_value_t *value, unsigned int indent);
+
+spdxtool_serialize_value_t *
+spdxtool_serialize_value_dup(const spdxtool_serialize_value_t *value);
+
+spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_take(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_value_t* value);
+
+spdxtool_serialize_object_list_t *
+spdxtool_serialize_object_list_new(void);
+
+spdxtool_serialize_array_t *
+spdxtool_serialize_array_new(void);
+
+spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_take(spdxtool_serialize_array_t *array, spdxtool_serialize_value_t* value);
+
+void
+spdxtool_serialize_value_free(spdxtool_serialize_value_t *value);
+
+void
+spdxtool_serialize_object_list_free(spdxtool_serialize_object_list_t *object_list);
+
+void
+spdxtool_serialize_object_free(spdxtool_serialize_object_t *object);
+
+void
+spdxtool_serialize_array_free(spdxtool_serialize_array_t *array);
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t * spdxtool_serialize_value_string(const char *s)
+ *
+ * Construct a JSON string value. The string is copied internally.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :param const char *s: String to copy. May be NULL, in which case the value holds NULL.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_string(const char *s)
+{
+ if (!s)
+ return NULL;
+
+ char *sv = strdup(s);
+ if (!sv)
+ return NULL;
+
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ {
+ free(sv);
+ return NULL;
+ }
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_STRING;
+ value->value.s = sv;
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_int(int d)
+ *
+ * Construct a JSON integer value.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :param int d: int value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_int(int i)
+{
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ return NULL;
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_INT;
+ value->value.i = i;
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_bool(bool b)
+ *
+ * Construct a JSON boolean value.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :param bool b: Boolean value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_bool(bool b)
+{
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ return NULL;
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_BOOL;
+ value->value.b = b;
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_null(void)
+ *
+ * Construct a JSON null value.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_null(void)
+{
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ return NULL;
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_NULL;
+ return value;
+}
+
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list)
+ *
+ * Construct a JSON object value wrapping an existing object list.
+ * The returned value takes ownership of the object list.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to wrap. Ownership transfers to the returned value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_object(spdxtool_serialize_object_list_t *object_list)
+{
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ return NULL;
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_OBJECT;
+ value->value.o = object_list;
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_value_array(spdxtool_serialize_array_t *array)
+ *
+ * Construct a JSON array value wrapping an existing array.
+ * The returned value takes ownership of the array.
+ * If this return value is not stolen, it must be freed with spdxtool_serialize_value_free().
+ *
+ * :param spdxtool_serialize_array_t *array: Array to wrap. Ownership transfers to the returned value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_value_array(spdxtool_serialize_array_t *array)
+{
+ spdxtool_serialize_value_t *value = calloc(1, sizeof(spdxtool_serialize_value_t));
+ if (!value)
+ return NULL;
+
+ value->type = SPDXTOOL_SERIALIZE_TYPE_ARRAY;
+ value->value.a = array;
+ return value;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value)
+ *
+ * Add a string key-value pair to a JSON object. The string is copied internally.
+ * Unconditionally adds the key even if value is NULL.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param const char *value: String value to copy.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_string(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value)
+ *
+ * Add a string key-value pair to a JSON object only if value is non-NULL.
+ * Use this for optional fields that should be omitted entirely when absent.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param const char *value: String value to copy, or NULL to skip.
+ * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the object.
+ * This object is not owned by the caller.
+ * If value is not set: NULL.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_string_opt(spdxtool_serialize_object_list_t *object_list, const char *key, const char *value)
+{
+ if (value)
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_string(value));
+
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value)
+ *
+ * Add a int key-value pair to a JSON object.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param int value: Integer value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_int(spdxtool_serialize_object_list_t *object_list, const char *key, int value)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_int(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value)
+ *
+ * Add a boolean key-value pair to a JSON object.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param bool value: Boolean value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_bool(spdxtool_serialize_object_list_t *object_list, const char *key, bool value)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_bool(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key)
+ *
+ * Add a null key-value pair to a JSON object.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_null(spdxtool_serialize_object_list_t *object_list, const char *key)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_null());
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value)
+ *
+ * Add an object key-value pair to a JSON object.
+ * This takes ownership of the object in value unconditionally, freeing on failure.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param spdxtool_serialize_object_list_t *value: Object value to add.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_object(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_object_list_t *value)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_object(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value)
+ *
+ * Add an array key-value pair to a JSON object.
+ * This takes ownership of the array in value unconditionally, freeing on failure.
+ *
+ * :param spdxtool_serialize_object_list_t *object_list: Object list to add to.
+ * :param const char *key: Key string.
+ * :param spdxtool_serialize_array_t *value: Array value to add.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the object.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_object_add_array(spdxtool_serialize_object_list_t *object_list, const char *key, spdxtool_serialize_array_t *value)
+{
+ return spdxtool_serialize_object_add_take(object_list, key, spdxtool_serialize_value_array(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value)
+ *
+ * Append a string value to a JSON array. The string is copied internally.
+ * Unconditionally appends even if value is NULL.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :param const char *value: String value to copy.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_string(spdxtool_serialize_array_t *array, const char *value)
+{
+ return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *a, const char *value)
+ *
+ * Append a string value to a JSON array only if value is non-NULL.
+ * Use this for optional array entries that should be omitted when absent.
+ *
+ * :param spdxtool_serialize_array_t *a: Array to append to.
+ * :param const char *value: String value to copy, or NULL to skip.
+ * :return: If value is set: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_STRING, located in the array.
+ * This object is not owned by the caller.
+ * If value is not set: NULL.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_string_opt(spdxtool_serialize_array_t *array, const char *value)
+{
+ if (value)
+ return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_string(value));
+
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value)
+ *
+ * Append a int value to a JSON array.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :param int value: integer value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_INT, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_int(spdxtool_serialize_array_t *array, int value)
+{
+ return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_int(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value)
+ *
+ * Append a boolean value to a JSON array.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :param bool value: Boolean value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_BOOL, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_bool(spdxtool_serialize_array_t *array, bool value)
+{
+ return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_bool(value));
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array)
+ *
+ * Append a null value to a JSON array.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_NULL, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_null(spdxtool_serialize_array_t *array)
+{
+ return spdxtool_serialize_array_add_take(array, spdxtool_serialize_value_null());
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value)
+ *
+ * Append an object value to a JSON array.
+ * This takes ownership of the object in value unconditionally, freeing on failure.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :param spdxtool_serialize_object_list_t *value: Object value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_OBJECT, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_object(spdxtool_serialize_array_t *array, spdxtool_serialize_object_list_t *value)
+{
+ if (!value)
+ return NULL;
+
+ spdxtool_serialize_value_t *ret = spdxtool_serialize_value_object(value);
+ if (!ret)
+ {
+ // Since we take possession of the pointer unconditionally, clean up.
+ spdxtool_serialize_object_list_free(value);
+ return NULL;
+ }
+
+ return spdxtool_serialize_array_add_take(array, ret);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value)
+ *
+ * Append an array value to a JSON array.
+ * This takes ownership of the array in value unconditionally, freeing on failure.
+ *
+ * :param spdxtool_serialize_array_t *array: Array to append to.
+ * :param spdxtool_serialize_array_t *value: Array value.
+ * :return: spdxtool_serialize_value_t * of type SPDXTOOL_SERIALIZE_TYPE_ARRAY, located in the array.
+ * This object is not owned by the caller.
+ */
+static inline spdxtool_serialize_value_t *
+spdxtool_serialize_array_add_array(spdxtool_serialize_array_t *array, spdxtool_serialize_array_t *value)
+{
+ if (!value)
+ return NULL;
+
+ spdxtool_serialize_value_t *ret = spdxtool_serialize_value_array(value);
+ if (!ret)
+ {
+ // Since we take possession of the pointer unconditionally, clean up.
+ spdxtool_serialize_array_free(value);
+ return NULL;
+ }
+
+ return spdxtool_serialize_array_add_take(array, ret);
+}
+
+spdxtool_serialize_value_t *
+spdxtool_serialize_sbom(pkgconf_client_t *client, spdxtool_core_agent_t *agent, spdxtool_core_creation_info_t *creation, spdxtool_core_spdx_document_t *spdx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/cli/spdxtool/simplelicensing.c b/cli/spdxtool/simplelicensing.c
new file mode 100644
index 000000000000..a9823b9b3b6c
--- /dev/null
+++ b/cli/spdxtool/simplelicensing.c
@@ -0,0 +1,113 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "util.h"
+#include "serialize.h"
+#include "software.h"
+#include "core.h"
+#include "simplelicensing.h"
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_simplelicensing_license_expression_t *spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, char *license)
+ *
+ * Create new /SimpleLicensing/SimpleLicensingText struct
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param char *license: SPDX name of license
+ * :return: NULL if some problem occurs and SimpleLicensingText struct if not
+ */
+spdxtool_simplelicensing_license_expression_t *
+spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license)
+{
+ if (!client || !license)
+ return NULL;
+
+ spdxtool_simplelicensing_license_expression_t *expression = calloc(1, sizeof(spdxtool_simplelicensing_license_expression_t));
+ if (!expression)
+ goto err;
+
+ expression->type = "simplelicensing_LicenseExpression";
+ expression->license_expression = strdup(license);
+ expression->spdx_id = spdxtool_util_get_spdx_id_string(client, expression->type, license);
+
+ if (!expression->license_expression || !expression->spdx_id)
+ goto err;
+
+ return expression;
+
+err:
+ pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_new: out of memory");
+ spdxtool_simplelicensing_licenseExpression_free(expression);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression)
+ *
+ * Free /SimpleLicensing/SimpleLicensingText struct
+ *
+ * :param spdxtool_simplelicensing_license_expression_t *expression: SimpleLicensingText struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression)
+{
+ if(!expression)
+ return;
+
+ free(expression->spdx_id);
+ free(expression->license_expression);
+
+ free(expression);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_simplelicensing_licenseExpression_to_object(const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression)
+ *
+ * Serialize /SimpleLicensing/LicenseExpression struct to a JSON value tree.
+ *
+ * :param const char *creation_info: The creationInfo ID string to embed in the object.
+ * :param const spdxtool_simplelicensing_license_expression_t *expression: LicenseExpression struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the LicenseExpression object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", "simplelicensing_LicenseExpression") &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", expression->spdx_id) &&
+ spdxtool_serialize_object_add_string(object_list, "simplelicensing_licenseExpression", expression->license_expression)))
+ {
+ goto err;
+ }
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_simplelicensing_licenseExpression_to_object: out of memory");
+
+ spdxtool_serialize_object_list_free(object_list);
+ return ret;
+}
diff --git a/cli/spdxtool/simplelicensing.h b/cli/spdxtool/simplelicensing.h
new file mode 100644
index 000000000000..328136c620c7
--- /dev/null
+++ b/cli/spdxtool/simplelicensing.h
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "util.h"
+
+#ifndef CLI__SPDXTOOL__SIMPLELICENSING_H
+#define CLI__SPDXTOOL__SIMPLELICENSING_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+spdxtool_simplelicensing_license_expression_t *
+spdxtool_simplelicensing_licenseExpression_new(pkgconf_client_t *client, const char *license);
+
+void
+spdxtool_simplelicensing_licenseExpression_free(spdxtool_simplelicensing_license_expression_t *expression);
+
+spdxtool_serialize_value_t *
+spdxtool_simplelicensing_licenseExpression_to_object(pkgconf_client_t *client, const char *creation_info, const spdxtool_simplelicensing_license_expression_t *expression);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/cli/spdxtool/software.c b/cli/spdxtool/software.c
new file mode 100644
index 000000000000..4e9712ba6942
--- /dev/null
+++ b/cli/spdxtool/software.c
@@ -0,0 +1,447 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <libpkgconf/libpkgconf.h>
+#include "util.h"
+#include "serialize.h"
+#include "software.h"
+#include "core.h"
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_software_sbom_t *spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type)
+ *
+ * Create new /Software/Sbom struct
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param const char *spdx_id: spdxId for this SBOM element
+ * :param const char *creation_id: id for CreationInfo
+ * :param const char *sbom_type: Sbom types can be found SPDX documention
+ * :return: NULL if some problem occurs and Sbom struct if not
+ */
+spdxtool_software_sbom_t *
+spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type)
+{
+ if (!client || !spdx_id || !creation_id || !sbom_type)
+ return NULL;
+
+ spdxtool_software_sbom_t *sbom = calloc(1, sizeof(spdxtool_software_sbom_t));
+ if (!sbom)
+ goto err;
+
+ sbom->type = "software_Sbom";
+ sbom->spdx_id = strdup(spdx_id);
+ sbom->creation_info = strdup(creation_id);
+ sbom->sbom_type = strdup(sbom_type);
+
+ if (!sbom->spdx_id || !sbom->creation_info || !sbom->sbom_type)
+ goto err;
+
+ return sbom;
+
+err:
+ pkgconf_error(client, "spdxtool_software_sbom_new: out of memory");
+ spdxtool_software_sbom_free(sbom);
+ return NULL;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom)
+ *
+ * Free /Software/Sbom struct
+ *
+ * :param spdxtool_software_sbom_t *sbom: Sbom struct to be freed.
+ * :return: nothing
+ */
+void
+spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom)
+{
+ if(!sbom)
+ return;
+
+ free(sbom->spdx_id);
+ free(sbom->creation_info);
+ free(sbom->sbom_type);
+
+ free(sbom);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom)
+ *
+ * Serialize /Software/Sbom struct to a JSON value tree. As a side effect,
+ * the package associated with the SBOM's rootElement is registered on the
+ * document via spdxtool_core_spdx_document_add_package, and relationship
+ * element IDs are registered via spdxtool_core_spdx_document_add_element.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param spdxtool_software_sbom_t *sbom: Sbom struct to be serialized.
+ * :return: spdxtool_serialize_value_t * representing the Sbom object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = NULL;
+ spdxtool_serialize_array_t *sbom_type_array = NULL;
+ spdxtool_serialize_array_t *root_element_array = NULL;
+ spdxtool_serialize_array_t *element_array = NULL;
+ char *spdx_id = NULL;
+
+ char sep = spdxtool_util_get_uri_separator(client);
+
+ spdx_id = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "spdxId");
+ if (!spdx_id)
+ goto err;
+
+ object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ sbom_type_array = spdxtool_serialize_array_new();
+ if (!sbom_type_array)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(sbom_type_array, sbom->sbom_type))
+ goto err;
+
+ root_element_array = spdxtool_serialize_array_new();
+ if (!root_element_array)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(root_element_array, spdx_id))
+ goto err;
+
+ element_array = spdxtool_serialize_array_new();
+ if (!element_array)
+ goto err;
+
+ pkgconf_node_t *node = NULL;
+ PKGCONF_FOREACH_LIST_ENTRY(sbom->rootElement->required.head, node)
+ {
+ pkgconf_dependency_t *dep = node->data;
+ pkgconf_pkg_t *match = dep->match;
+ pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", sbom->rootElement->id, sep, sep, match->id);
+ char *relationship_str = pkgconf_buffer_freeze(&relationship_buf);
+ if (!relationship_str)
+ goto err;
+
+ char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str);
+ free(relationship_str);
+ if (!spdx_id_relation)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(element_array, spdx_id_relation))
+ {
+ free(spdx_id_relation);
+ goto err;
+ }
+
+ if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, spdx_id_relation))
+ {
+ free(spdx_id_relation);
+ goto err;
+ }
+
+ free(spdx_id_relation);
+ }
+
+ char *value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasDeclaredLicense");
+ if (value)
+ {
+ if (!spdxtool_serialize_array_add_string(element_array, value))
+ {
+ free(value);
+ goto err;
+ }
+
+ if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value))
+ {
+ free(value);
+ goto err;
+ }
+
+ free(value);
+ }
+
+ value = spdxtool_util_tuple_lookup(client, &sbom->rootElement->vars, "hasConcludedLicense");
+ if (value)
+ {
+ if (!spdxtool_serialize_array_add_string(element_array, value))
+ {
+ free(value);
+ goto err;
+ }
+
+ if (!spdxtool_core_spdx_document_add_element(client, sbom->spdx_document, value))
+ {
+ free(value);
+ goto err;
+ }
+
+ free(value);
+ }
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", sbom->type) &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", sbom->creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", sbom->spdx_id)))
+ {
+ goto err;
+ }
+
+ if (!spdxtool_serialize_object_add_array(object_list, "software_sbomType", sbom_type_array))
+ goto err;
+ sbom_type_array = NULL;
+
+ if (!spdxtool_serialize_object_add_array(object_list, "rootElement", root_element_array))
+ goto err;
+ root_element_array = NULL;
+
+ if (!spdxtool_serialize_object_add_array(object_list, "element", element_array))
+ goto err;
+ element_array = NULL;
+
+ if (!spdxtool_core_spdx_document_add_package(client, sbom->spdx_document, sbom->rootElement))
+ goto err;
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_software_sbom_to_object: out of memory");
+
+ free(spdx_id);
+ spdxtool_serialize_object_list_free(object_list);
+ spdxtool_serialize_array_free(sbom_type_array);
+ spdxtool_serialize_array_free(root_element_array);
+ spdxtool_serialize_array_free(element_array);
+ return ret;
+}
+
+static bool
+serialize_copyright_lines_to_object(spdxtool_serialize_object_list_t *object_list, const pkgconf_list_t *copyright_lines)
+{
+ pkgconf_buffer_t copyright_buf = PKGCONF_BUFFER_INITIALIZER;
+ const pkgconf_node_t *node;
+
+ if (copyright_lines->head == NULL)
+ return spdxtool_serialize_object_add_string(object_list, "software_copyrightText", "NOASSERTION") != NULL;
+
+ PKGCONF_FOREACH_LIST_ENTRY(copyright_lines->head, node)
+ {
+ const pkgconf_bufferset_t *set = node->data;
+ pkgconf_buffer_join(&copyright_buf, '\n', pkgconf_buffer_str_or_empty(&set->buffer), NULL);
+ }
+
+ bool ok = spdxtool_serialize_object_add_string(object_list, "software_copyrightText", pkgconf_buffer_str_or_empty(&copyright_buf)) != NULL;
+ pkgconf_buffer_finalize(&copyright_buf);
+ return ok;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: spdxtool_serialize_value_t *spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx)
+ *
+ * Serialize /Software/Package struct to a JSON value tree. As a side effect,
+ * any license and dependency relationships generated during serialization are
+ * added to the document via spdxtool_core_spdx_document_add_relationship.
+ *
+ * :param pkgconf_client_t *client: The pkgconf client being accessed.
+ * :param pkgconf_pkg_t *pkg: Package struct to be serialized.
+ * :param spdxtool_core_spdx_document_t *spdx: SpdxDocument to which generated relationships are added.
+ * :return: spdxtool_serialize_value_t * representing the Package object.
+ */
+spdxtool_serialize_value_t *
+spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *spdx)
+{
+ spdxtool_serialize_value_t *ret = NULL;
+ spdxtool_serialize_object_list_t *object_list = NULL;
+ spdxtool_serialize_array_t *originated_by = NULL;
+ char *creation_info = NULL;
+ char *spdx_id = NULL;
+ char *agent = NULL;
+ char *spdx_id_license = NULL;
+ pkgconf_list_t relations = PKGCONF_LIST_INITIALIZER;
+ pkgconf_list_t *cpy_relations = NULL;
+ pkgconf_node_t *node = NULL;
+ char sep = spdxtool_util_get_uri_separator(client);
+
+ creation_info = spdxtool_util_tuple_lookup(client, &pkg->vars, "creationInfo");
+ spdx_id = spdxtool_util_tuple_lookup(client, &pkg->vars, "spdxId");
+ agent = spdxtool_util_tuple_lookup(client, &pkg->vars, "agent");
+
+ if (!creation_info || !spdx_id || !agent)
+ goto err;
+
+ object_list = spdxtool_serialize_object_list_new();
+ if (!object_list)
+ goto err;
+
+ originated_by = spdxtool_serialize_array_new();
+ if (!originated_by)
+ goto err;
+
+ if (!spdxtool_serialize_array_add_string(originated_by, agent))
+ goto err;
+
+ if (!(spdxtool_serialize_object_add_string(object_list, "type", "software_Package") &&
+ spdxtool_serialize_object_add_string(object_list, "creationInfo", creation_info) &&
+ spdxtool_serialize_object_add_string(object_list, "spdxId", spdx_id) &&
+ spdxtool_serialize_object_add_string(object_list, "name", pkg->realname)))
+ {
+ goto err;
+ }
+
+ if (!spdxtool_serialize_object_add_array(object_list, "originatedBy", originated_by))
+ goto err;
+ originated_by = NULL;
+
+ if (!serialize_copyright_lines_to_object(object_list, &pkg->copyright))
+ goto err;
+
+ if (!spdxtool_serialize_object_add_string(object_list, "software_homePage",
+ pkg->url ? pkg->url : ""))
+ {
+ goto err;
+ }
+
+ if (!spdxtool_serialize_object_add_string(object_list, "software_downloadLocation",
+ pkg->source ? pkg->source : ""))
+ {
+ goto err;
+ }
+
+ if (!spdxtool_serialize_object_add_string(object_list, "software_packageVersion", pkg->version))
+ goto err;
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->license.head, node)
+ {
+ const pkgconf_license_t *license = node->data;
+ if (license->type == PKGCONF_LICENSE_EXPRESSION)
+ {
+ spdx_id_license = spdxtool_util_get_spdx_id_string(client, "simplelicensing_LicenseExpression", license->data);
+ if (!spdx_id_license)
+ goto err;
+
+ pkgconf_license_insert(client, &relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_license);
+ free(spdx_id_license);
+ spdx_id_license = NULL;
+ }
+ }
+
+ char *tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasDeclaredLicense");
+ if (tuple_license)
+ {
+ cpy_relations = calloc(1, sizeof(pkgconf_list_t));
+ if (!cpy_relations)
+ {
+ free(tuple_license);
+ goto err;
+ }
+
+ pkgconf_license_copy_list(client, cpy_relations, &relations);
+ spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasDeclaredLicense");
+ free(tuple_license);
+ if (!relationship)
+ goto err;
+ if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship))
+ goto err;
+ cpy_relations = NULL;
+ }
+
+ tuple_license = spdxtool_util_tuple_lookup(client, &pkg->vars, "hasConcludedLicense");
+ if (tuple_license)
+ {
+ cpy_relations = calloc(1, sizeof(pkgconf_list_t));
+ if (!cpy_relations)
+ {
+ free(tuple_license);
+ goto err;
+ }
+
+ pkgconf_license_copy_list(client, cpy_relations, &relations);
+ spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, tuple_license, spdx_id, cpy_relations, "hasConcludedLicense");
+ free(tuple_license);
+ if (!relationship)
+ goto err;
+ if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship))
+ goto err;
+ cpy_relations = NULL;
+ }
+ pkgconf_license_free(&relations);
+
+ PKGCONF_FOREACH_LIST_ENTRY(pkg->required.head, node)
+ {
+ pkgconf_dependency_t *dep = node->data;
+ pkgconf_pkg_t *match = dep->match;
+ pkgconf_buffer_t relationship_buf = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_append_fmt(&relationship_buf, "%s%cdependsOn%c%s", pkg->id, sep, sep, match->id);
+ char *relationship_str = pkgconf_buffer_freeze(&relationship_buf);
+ if (!relationship_str)
+ goto err;
+
+ char *spdx_id_relation = spdxtool_util_get_spdx_id_string(client, "Relationship", relationship_str);
+ free(relationship_str);
+ if (!spdx_id_relation)
+ goto err;
+
+ char *spdx_id_package = spdxtool_util_get_spdx_id_string(client, "Package", match->id);
+ if (!spdx_id_package)
+ {
+ free(spdx_id_relation);
+ goto err;
+ }
+
+ cpy_relations = calloc(1, sizeof(pkgconf_list_t));
+ if (!cpy_relations)
+ {
+ free(spdx_id_relation);
+ free(spdx_id_package);
+ goto err;
+ }
+
+ pkgconf_license_insert(client, cpy_relations, PKGCONF_LICENSE_UNKNOWN, spdx_id_package);
+ spdxtool_core_relationship_t *relationship = spdxtool_core_relationship_new(client, creation_info, spdx_id_relation, spdx_id, cpy_relations, "dependsOn");
+ free(spdx_id_relation);
+ free(spdx_id_package);
+ if (!relationship)
+ goto err;
+ if (!spdxtool_core_spdx_document_add_relationship(client, spdx, relationship))
+ goto err;
+ cpy_relations = NULL;
+ }
+
+ ret = spdxtool_serialize_value_object(object_list);
+ object_list = NULL;
+
+err:
+ if (!ret)
+ pkgconf_error(client, "spdxtool_software_package_to_object: out of memory");
+
+ free(creation_info);
+ free(spdx_id);
+ free(agent);
+ free(spdx_id_license);
+ spdxtool_serialize_object_list_free(object_list);
+ spdxtool_serialize_array_free(originated_by);
+ return ret;
+}
diff --git a/cli/spdxtool/software.h b/cli/spdxtool/software.h
new file mode 100644
index 000000000000..d18710cb0d1b
--- /dev/null
+++ b/cli/spdxtool/software.h
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#ifndef CLI__SPDXTOOL_SOFTWARE_H
+#define CLI__SPDXTOOL_SOFTWARE_H
+
+#include <stdlib.h>
+#include "util.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+spdxtool_software_sbom_t *
+spdxtool_software_sbom_new(pkgconf_client_t *client, const char *spdx_id, const char *creation_id, const char *sbom_type);
+
+void
+spdxtool_software_sbom_free(spdxtool_software_sbom_t *sbom);
+
+spdxtool_serialize_value_t *
+spdxtool_software_package_to_object(pkgconf_client_t *client, pkgconf_pkg_t *pkg, spdxtool_core_spdx_document_t *doc);
+
+spdxtool_serialize_value_t *
+spdxtool_software_sbom_to_object(pkgconf_client_t *client, spdxtool_software_sbom_t *sbom);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/cli/spdxtool/util.c b/cli/spdxtool/util.c
new file mode 100644
index 000000000000..e47bf0a1e61f
--- /dev/null
+++ b/cli/spdxtool/util.c
@@ -0,0 +1,331 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#include <libpkgconf/libpkgconf.h>
+#include <libpkgconf/stdinc.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+#include "util.h"
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default)
+ *
+ * Set key wit default value. Default value is used if key is NULL
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param const char *key: Key to be preserved
+ * :param const char *key_value: Value for key
+ * :param const char *key_default: Default value if key_value is NULL
+ * :return: nothing
+ */
+void
+spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default)
+{
+ PKGCONF_TRACE(client, "set uri_root to: %s", key_value != NULL ? key_value : key_default);
+ pkgconf_tuple_add_global(client, key, key_value != NULL ? key_value : key_default);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root)
+ *
+ * Set URI/URL root for spdxId. Type for this is 'xsd:anyURI' which means
+ * it can they may be absolute or relative.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param const char *uri_root: URI root.
+ * :return: nothing
+ */
+void
+spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root)
+{
+ spdxtool_util_set_key(client, "spdx_uri_root", uri_root, "https://github.com/pkgconf/pkgconf");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *spdxtool_util_get_uri_root(pkgconf_client_t *client)
+ *
+ * Get current URI
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :return: URI or NULL
+ */
+const char *
+spdxtool_util_get_uri_root(pkgconf_client_t *client)
+{
+ return pkgconf_tuple_find_global(client, "spdx_uri_root");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon)
+ *
+ * when using URI rather than URL change separator from slash '/' to colon ':'.
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param bool is_colon If true use colon ':' is separator and otherwise slash '/'
+ * :return: nothing
+ */
+void
+spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool is_colon)
+{
+ if (is_colon == true)
+ client->flags |= SPDXTOOL_SEPARATOR_COLON;
+ else
+ client->flags = client->flags & ~SPDXTOOL_SEPARATOR_COLON;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char spdxtool_util_get_uri_separator(pkgconf_client_t *client)
+ *
+ * Get separator char
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :return: Colon ':' or slash '/'
+ */
+char
+spdxtool_util_get_uri_separator(pkgconf_client_t *client)
+{
+ if (client->flags & SPDXTOOL_SEPARATOR_COLON)
+ return ':';
+
+ return '/';
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version)
+ *
+ * Set current SPDX SBOM Spec version. If not set it's 3.0.1
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param char* spdx_version: SPDX specification version.
+ * :return: nothing
+ */
+void
+spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version)
+{
+ spdxtool_util_set_key(client, "spdx_version", spdx_version, "3.0.1");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *spdxtool_util_get_spdx_version(pkgconf_client_t *client)
+ *
+ * Get SPDX SBOM specification version in use
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :return: Spec version
+ */
+const char *
+spdxtool_util_get_spdx_version(pkgconf_client_t *client)
+{
+ return pkgconf_tuple_find_global(client, "spdx_version");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: void spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license)
+ *
+ * Under which license SBOM is released. License string should be in
+ * SPDX style. Something like: FreeBSD-DOC, CC0-1.0 or MIT
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param const char *spdx_license: SPDX compatible license
+ * :return: nothing
+ */
+void
+spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license)
+{
+ spdxtool_util_set_key(client, "spdx_license", spdx_license, "CC0-1.0");
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: const char *spdxtool_util_get_spdx_license(pkgconf_client_t *client)
+ *
+ * Get license which SBOM is release in
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :return: SPDX license
+ */
+const char *
+spdxtool_util_get_spdx_license(pkgconf_client_t *client)
+{
+ return pkgconf_tuple_find_global(client, "spdx_license");
+}
+
+static size_t last_id = 0;
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, char *part)
+ *
+ * Get spdxId with current URI.
+ * URI is lookg like https://test/part/1
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param char *part: Addition subdir part before number
+ * :return: URI
+ */
+char *
+spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part)
+{
+ const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client);
+ char sep = spdxtool_util_get_uri_separator(client);
+ pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_join(&current_uri, sep, global_xsd_any_uri, part, NULL);
+ pkgconf_buffer_append_fmt(&current_uri, "%c" SIZE_FMT_SPECIFIER, sep, ++last_id);
+
+ return pkgconf_buffer_freeze(&current_uri);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id)
+ *
+ * Get string id URI
+ * looks something like: https://test/part/string_id
+ *
+ * :param pkgconf_client_t* client: The pkgconf client being accessed.
+ * :param const char* part: subdir part of URI.
+ * :param const char* string_id: String ID.
+ * :return: URI
+ */
+char *
+spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id)
+{
+ const char *global_xsd_any_uri = spdxtool_util_get_uri_root(client);
+ char sep = spdxtool_util_get_uri_separator(client);
+ pkgconf_buffer_t current_uri = PKGCONF_BUFFER_INITIALIZER;
+
+ pkgconf_buffer_join(&current_uri, sep, global_xsd_any_uri, part, string_id, NULL);
+
+ return pkgconf_buffer_freeze(&current_uri);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_get_iso8601_time(time_t *wanted_time)
+ *
+ * Get ISO8601 time string
+ *
+ * :param time_t *wanted_time: Time to be converted
+ * :return: Time string in ISO8601 format
+ */
+char *
+spdxtool_util_get_iso8601_time(time_t *wanted_time)
+{
+ char buf[PKGCONF_ITEM_SIZE];
+
+ if (!wanted_time)
+ return NULL;
+
+ struct tm *tm_info = gmtime(wanted_time);
+ if (!tm_info)
+ return NULL;
+
+ /* ISO8601 time with Z at the end */
+ strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", tm_info);
+ return strdup(buf);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_get_current_iso8601_time(void)
+ *
+ * Get ISO8601 current timestamp
+ *
+ * :return: Time string in ISO8601 format
+ */
+char *
+spdxtool_util_get_current_iso8601_time(void)
+{
+ time_t now;
+ time(&now);
+ return spdxtool_util_get_iso8601_time(&now);
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_string_correction(char *str)
+ *
+ * Lowercase string and change spaces to '_'
+ *
+ * :param char *str: String to be converted
+ * :return: Converted string
+ */
+char *
+spdxtool_util_string_correction(char *str)
+{
+ char *ptr = str;
+ /* Lowecase string and make spaces '_' */
+ for ( ; *ptr; ++ptr)
+ {
+ *ptr = (char) tolower(*ptr);
+ if(isspace((unsigned char) *ptr))
+ {
+ *ptr = '_';
+ }
+ }
+
+ return str;
+}
+
+/*
+ * !doc
+ *
+ * .. c:function:: char *spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key)
+ *
+ * Lowercase string and change spaces to '_'
+ *
+ * :param pkgconf_client_t *client: client to use for the lookup
+ * :param pkgconf_list_t *vars: locally-scoped vars to look through
+ * :param char *key: key to lookup
+ * :return: Converted string
+ */
+char *
+spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key)
+{
+ pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
+ pkgconf_bytecode_eval_ctx_t ctx = {
+ .client = client,
+ .vars = vars,
+ };
+
+ pkgconf_variable_t *v = pkgconf_bytecode_eval_lookup_var(&ctx, key, strlen(key));
+ if (v == NULL)
+ return NULL;
+
+ if (!pkgconf_variable_eval(client, vars, v, &buf, NULL))
+ return NULL;
+
+ return pkgconf_buffer_freeze(&buf);
+}
diff --git a/cli/spdxtool/util.h b/cli/spdxtool/util.h
new file mode 100644
index 000000000000..be86d6e154d7
--- /dev/null
+++ b/cli/spdxtool/util.h
@@ -0,0 +1,130 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ *​ Copyright (c) 2025 The FreeBSD Foundation
+ *​
+ *​ Portions of this software were developed by
+ * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from
+ * the FreeBSD Foundation
+ */
+
+#ifndef CLI__SPDXTOOL__UTIL_H
+#define CLI__SPDXTOOL__UTIL_H
+
+#include <libpkgconf/libpkgconf.h>
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SPDXTOOL_SEPARATOR_COLON 0x80
+
+typedef struct spdxtool_core_agent_
+{
+ char *spdx_id;
+ const char *type;
+ char *creation_info;
+ char *name;
+} spdxtool_core_agent_t;
+
+typedef struct spdxtool_core_creation_info_
+{
+ char *id;
+ const char *type;
+ char *created;
+ char *created_by;
+ const char *created_using;
+ char *spec_version;
+} spdxtool_core_creation_info_t;
+
+typedef struct spdxtool_core_spdx_document
+{
+ const char *type;
+ char *spdx_id;
+ char *creation_info;
+ char *agent;
+ pkgconf_list_t licenses;
+ pkgconf_list_t element;
+ pkgconf_list_t rootElement;
+ pkgconf_list_t relationships;
+ pkgconf_list_t packages;
+} spdxtool_core_spdx_document_t;
+
+typedef struct spdxtool_software_sbom_
+{
+ char *spdx_id;
+ const char *type;
+ char *creation_info;
+ char *sbom_type;
+ spdxtool_core_spdx_document_t *spdx_document;
+ pkgconf_pkg_t *rootElement;
+} spdxtool_software_sbom_t;
+
+typedef struct spdxtool_simplelicensing_license_expression_
+{
+ const char *type;
+ char *spdx_id;
+ char *license_expression;
+} spdxtool_simplelicensing_license_expression_t;
+
+typedef struct spdxtool_core_relationship_
+{
+ const char *type;
+ char *spdx_id;
+ char *creation_info;
+ char *from;
+ pkgconf_list_t *to;
+ char *relationship_type;
+} spdxtool_core_relationship_t;
+
+void
+spdxtool_util_set_key(pkgconf_client_t *client, const char *key, const char *key_value, const char *key_default);
+
+void
+spdxtool_util_set_uri_root(pkgconf_client_t *client, const char *uri_root);
+
+void
+spdxtool_util_set_uri_separator_colon(pkgconf_client_t *client, bool sep);
+
+char
+spdxtool_util_get_uri_separator(pkgconf_client_t *client);
+
+const char *
+spdxtool_util_get_uri_root(pkgconf_client_t *client);
+
+void
+spdxtool_util_set_spdx_version(pkgconf_client_t *client, const char *spdx_version);
+
+const char *
+spdxtool_util_get_spdx_version(pkgconf_client_t *client);
+
+void
+spdxtool_util_set_spdx_license(pkgconf_client_t *client, const char *spdx_license);
+
+const char *
+spdxtool_util_get_spdx_license(pkgconf_client_t *client);
+
+char *
+spdxtool_util_get_spdx_id_int(pkgconf_client_t *client, const char *part);
+
+char *
+spdxtool_util_get_spdx_id_string(pkgconf_client_t *client, const char *part, const char *string_id);
+
+char *
+spdxtool_util_get_iso8601_time(time_t *wanted_time);
+
+char *
+spdxtool_util_get_current_iso8601_time(void);
+
+char *
+spdxtool_util_string_correction(char *str);
+
+char *
+spdxtool_util_tuple_lookup(pkgconf_client_t *client, pkgconf_list_t *vars, const char *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif