pax_global_header 0000666 0000000 0000000 00000000064 15002525747 0014521 g ustar 00root root 0000000 0000000 52 comment=e57b719b709f9112b2570a914e5efef7faf0ff03
u-config-0.34.0/ 0000775 0000000 0000000 00000000000 15002525747 0013314 5 ustar 00root root 0000000 0000000 u-config-0.34.0/Makefile 0000664 0000000 0000000 00000007704 15002525747 0014764 0 ustar 00root root 0000000 0000000 # NOTE TO DISTRIBUTION PACKAGE MAINTAINERS: The targets in this Makefile
# are intended only for development, testing, and evaluation. If you are
# packaging u-config for a distribution, invoke a compiler directly on
# the appropriate main_*.c source. See "Build" in README.md for more
# information, and "Configuration" for a list of configuration options.
CROSS = x86_64-w64-mingw32-
CC = gcc
OPT = -Os
PC = pkg-config # e.g. for CROSS-pkg-config
DEBUG_CFLAGS = -g3 -Wall -Wextra -Wconversion \
-fsanitize=undefined -fsanitize-undefined-trap-on-error
WIN32_CFLAGS = -fno-builtin -fno-asynchronous-unwind-tables
WIN32_LIBS = -s -nostdlib -Wl,--gc-sections -lkernel32
LINUX_CFLAGS = -fno-builtin -fno-pie -fno-asynchronous-unwind-tables
LINUX_LIBS = -static -s -no-pie -nostdlib -Wl,--gc-sections
default:
@echo 'No sensible default target. Makefile is intended for development'
@echo 'and testing. See "Build" and "Configuration" in README.md. Use the'
@echo '"pkg-config" target to copy your system pkg-config configuration'
@echo 'for evaluation and comparison.'
@exit 1
src_windows = src/cmdline.c src/miniwin32.h src/u-config.c
src_linux = src/linux_noarch.c src/u-config.c
pkg-config.exe: main_windows.c $(src_windows)
$(CROSS)$(CC) $(OPT) $(WIN32_CFLAGS) -o $@ main_windows.c $(WIN32_LIBS)
pkg-config-debug.exe: main_windows.c $(src_windows)
$(CROSS)$(CC) -nostartfiles $(DEBUG_CFLAGS) -o $@ main_windows.c
# Auto-configure using the system's pkg-config search path
pkg-config: main_posix.c src/u-config.c
$(CC) $(OPT) -o $@ main_posix.c \
-DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\""
pkg-config-debug: main_posix.c src/u-config.c
$(CC) $(DEBUG_CFLAGS) -o $@ main_posix.c
# Auto-configure using the system's pkg-config search path
pkg-config-linux-amd64: main_linux_amd64.c $(src_linux)
$(CC) $(OPT) $(LINUX_CFLAGS) -o $@ main_linux_amd64.c $(LINUX_LIBS) \
-DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\""
pkg-config-linux-amd64-debug: main_linux_amd64.c $(src_linux)
$(CC) -nostdlib -fno-builtin $(DEBUG_CFLAGS) -o $@ main_linux_amd64.c
# Auto-configure using the system's pkg-config search path
pkg-config-linux-i686: main_linux_i686.c $(src_linux)
$(CC) $(OPT) $(LINUX_CFLAGS) -o $@ main_linux_i686.c $(LINUX_LIBS) \
-DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\""
pkg-config-linux-i686-debug: main_linux_i686.c $(src_linux)
$(CC) -nostdlib -fno-builtin $(DEBUG_CFLAGS) -o $@ main_linux_i686.c
# Concatenate Windows-only u-config into a single source file
amalgamation: pkg-config.c
pkg-config.c: main_windows.c $(src_windows)
awk 'n{print"";n=0} NR==3{printf"%s\n",cc} !/^#i.*"/{print}' \
cc='// $$ cc -nostartfiles -o pkg-config.exe pkg-config.c' \
>$@ src/u-config.c n=1 \
src/miniwin32.h n=1 \
src/cmdline.c n=1 \
main_windows.c
release:
version=$$(git describe); prefix=u-config-$${version#v}; \
git archive --prefix=$$prefix/ HEAD | gzip -9 >$$prefix.tar.gz
tests.exe: main_test.c src/u-config.c
$(CROSS)$(CC) $(DEBUG_CFLAGS) -Wno-clobbered -o $@ main_test.c
tests: main_test.c src/u-config.c
$(CC) $(DEBUG_CFLAGS) -Wno-clobbered -o $@ main_test.c
pkg-config.wasm: main_wasm.c src/u-config.c
clang --target=wasm32 -nostdlib -Os -mbulk-memory \
-Wall -Wextra -Wconversion -Wno-unused-parameter \
-s -Wl,--stack-first -o $@ main_wasm.c
check test: tests$(EXE)
./tests$(EXE)
# Build and install into w64devkit
install: main_windows.c $(src_windows)
$(CROSS)$(CC) $(OPT) $(WIN32_CFLAGS) main_windows.c \
-DPKG_CONFIG_PREFIX="\"/$$(gcc -dumpmachine)\"" \
-o $(W64DEVKIT_HOME)/bin/pkg-config.exe $(WIN32_LIBS)
clean:
rm -f pkg-config.exe pkg-config-debug.exe \
pkg-config pkg-config-debug \
pkg-config-linux-amd64 pkg-config-linux-amd64-debug \
pkg-config-linux-i686 pkg-config-linux-i686-debug \
pkg-config.c u-config-*.tar.gz \
tests.exe tests pkg-config.wasm \
*.ilk *.obj *.pdb main_test.exe
u-config-0.34.0/README.md 0000664 0000000 0000000 00000017012 15002525747 0014574 0 ustar 00root root 0000000 0000000 # u-config: a small, simple, portable pkg-config clone
u-config ("*micro*-config") is a small, highly portable [pkg-config][] and
[pkgconf][] clone. It was written primarily for [w64devkit][] and Windows,
but can also serve as a reliable drop-in replacement on other platforms.
Notable features:
* A fraction of the size while retaining the core, user-level features of
pkg-config and pkgconf.
* Windows as a first-class supported platform.
* Highly portable to any machine. Supports a variety of compilers and
operating systems, ancient and new. [Even WebAssembly][wasm]! Can be
built without libc, which is handy for bootstrapping.
* Trivial, fast build. No messing around with GNU Autotools, or any build
system for that matter.
Each pkg-config is specialized to its surrounding software distribution,
and so **u-config is probably only useful to distribution maintainers**.
As noted, u-config focuses on *user-level* features but deliberately lacks
*developer-level* features. The goal is to **support existing pkg-config
based builds, not make more of them**. In summary:
* Omits most `.pc` debugging features (`--print-…`)
* No special handling of "uninstalled" packages, and no attendant knobs
* Skips checks unimplemented by pkg-config (i.e. `Conflicts:`)
* Less strict `.pc` syntax
It still supports the important pkg-config run-time environment variables:
* `PKG_CONFIG_PATH`
* `PKG_CONFIG_LIBDIR`
* `PKG_CONFIG_TOP_BUILD_DIR`
* `PKG_CONFIG_SYSTEM_INCLUDE_PATH`
* `PKG_CONFIG_SYSTEM_LIBRARY_PATH`
* `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS`
* `PKG_CONFIG_ALLOW_SYSTEM_LIBS`
See the pkg-config documentation for details. It also closely follows
pkg-config's idiosyncratic argument parsing — positional arguments are
concatenated then retokenzied — and its undocumented `.pc` quote and
backslash syntax.
## Features unique to u-config
* `--newlines`: separates arguments by line feeds instead of spaces, which
is sometimes useful like in the fish shell or when manually examining
output.
* Handles spaces in `prefix`: Especially important on Windows where spaces
in paths are common. Libraries will work correctly even when installed
under such a path. (Note: Despite popular belief, and the examples in
its own documentation, *pkg-config was never designed for use in command
substitution*. It was designed for `eval`, and spaces in `prefix` will
require implicit or explicit `eval`.)
## Build
u-config compiles as one translation unit. Choose an appropriate platform
layer (`main_*.c`) for your target then invoke your C compiler only that
source file.
$ cc -Os -o pkg-config main_posix.c
Windows has a dedicated platform layer which understands Unicode paths and
environment variables — though mind that it's probably interacting with
tools that do not. It outputs UTF-8 regardless of the system code page. In
general, u-config correctly processes paths with spaces, a feature that is
especially important on Windows. Do not link a C runtime (CRT) in this
configuration:
$ cc -Os -nostartfiles -o pkg-config main_windows.c
Or with MSVC:
$ cl /GS- /O2 /Os /Fe:pkg-config main_msvc.c
Or a WebAssembly (WASI) build:
$ clang --target=wasm32 -nostdlib -Os -o pkg-config.wasm main_wasm.c
The Makefile documents compiler options for a more aggressively optimized
GCC-based build.
## Configuration
The default search path tries to be useful, but the `pkgconfig` directory
locations are unpredictable and largely distribution-specific. Once known,
use the macros below to hard code the correct paths into the binary. To
double check your configuration, examine `pc_path`:
$ ./pkg-config --variable pc_path pkg-config
The `pkg-config` package is virtual and need not exist as an actual `.pc`
file installed in the system. The `pkg-config` make target automatically
grabs the search path from the system `pkg-config`, and so could then be a
drop-in replacement for it.
$ make pkg-config # grabs system's pkg-config search path
### POSIX configuration options
The POSIX platform has several compile time configuration parameters.
Each must be formatted as a C string with quotes. Relative paths will be
relative to the run-time working directory, not the installation prefix,
which is usually not useful.
* `PKG_CONFIG_PATH`: Like the run-time environment variable, but places
these additional paths ahead of the standard search locations. If the
standard search locations are insufficient, this is probably what you
want.
* `PKG_CONFIG_PREFIX` (default `"/usr"`): The default prefix for the four
standard search locations.
* `PKG_CONFIG_LIBDIR`: Like the run-time environment variable, but fully
configures the fixed, static search path. If set, `PKG_CONFIG_PREFIX` is
not used. If the environment variable is set at run time, it replaces
this path.
* `PKG_CONFIG_DEFINE_PREFIX`: Sets the default for `--define-prefix` /
`--dont-define-prefix`. Defaults to true on Windows, false otherwise.
You probably want the default.
* `PKG_CONFIG_SYSTEM_INCLUDE_PATH`: Like the run-time environment variable
but sets the static default.
* `PKG_CONFIG_SYSTEM_LIBRARY_PATH`: Like the run-time environment variable
but sets the static default.
Examples:
$ cc -DPKG_CONFIG_PREFIX="\"$HOME/.local\"" ...
$ cc -DPKG_CONFIG_PATH="\"$HOME/.local/lib/pkgconfig\"" ...
### Windows configuration options
The "win32" platform always searches relative to `pkg-config.exe` since
it's essentially the only useful behavior. It is rare for anything aside
from system components to be at compile-time-known absolute paths. The
default configuration assumes pkg-config resides in `bin/` adjacent to
`lib/` and `share/` — a typical sysroot.
* `PKG_CONFIG_PREFIX`: A relative component placed before the "standard"
search paths (`lib/pkgconfig`, `share/pkgconfig`). Either slash or
backslash may be used to separate path components, but slash reduces
problems.
Example, if just outside a sysroot which identifies the architecture:
$ gcc -DPKG_CONFIG_PREFIX=\"/$(gcc -dumpmachine)\" ...
While both are supported, u-config prefers slashes over backslashes in
order to reduce issues involving backslash as a shell metacharacter.
### libc-free Linux configuration options
`main_linux_*.c` makes direct Linux system calls using assembly and does
not require libc. It compiles to a ~20kB static executable that will work
on any Linux distribution. It supports these configuration macros with the
same behavior as the POSIX platform.
* `PKG_CONFIG_LIBDIR`
* `PKG_CONFIG_SYSTEM_INCLUDE_PATH`
* `PKG_CONFIG_SYSTEM_LIBRARY_PATH`
### Debugging
Suggested debug build, intended to be run under a debugger:
$ cc -g3 -Wall -Wextra -Wconversion -Wno-sign-conversion \
-fsanitize=undefined -fsanitize-trap main_PLATFORM.c
Enabling Undefined Behavior Sanitizer also enables assertions.
### Test suite
The test suite is a libc-based platform layer and runs u-config through
its entry point in various configurations on a virtual file system. Either
build and run `main_test.c` as a platform, or use the suggested test
configuration in the Makefile (set `EXE=.exe` on Windows):
$ make check
### Fuzz testing
`main_fuzz.c` is a platform layer implemented on top of [AFL++][]. Fuzzer
input is supplied through a virtual file, and it exercises `.pc` parsing,
variable expansion, and output processing. Its header documents suggested
usage.
[AFL++]: https://github.com/AFLplusplus/AFLplusplus
[pkg-config]: https://www.freedesktop.org/wiki/Software/pkg-config/
[pkgconf]: http://pkgconf.org/
[w64devkit]: https://github.com/skeeto/w64devkit
[wasm]: https://skeeto.github.io/u-config/
u-config-0.34.0/UNLICENSE 0000664 0000000 0000000 00000002273 15002525747 0014570 0 ustar 00root root 0000000 0000000 This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
u-config-0.34.0/main_fuzz.c 0000664 0000000 0000000 00000002673 15002525747 0015472 0 ustar 00root root 0000000 0000000 // afl fuzz test platform layer for u-config
// $ afl-gcc-fast -fsanitize=undefined -fsanitize-trap main_fuzz.c
// $ afl-fuzz -i /usr/share/pkgconfig -o fuzzout ./a.out
#define FUZZTEST
#include "src/u-config.c"
#include
#include
#include // required by afl
__AFL_FUZZ_INIT();
struct os {
jmp_buf ret;
s8 pcfile;
};
static void os_fail(os *ctx)
{
longjmp(ctx->ret, 1);
}
static void os_write(os *ctx, i32 fd, s8 s)
{
(void)ctx;
(void)fd;
(void)s;
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
(void)perm;
(void)path;
filemap r = {0};
r.data = ctx->pcfile;
r.status = filemap_OK;
return r;
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
(void)ctx;
(void)a;
(void)path;
assert(0);
}
int main(void)
{
__AFL_INIT();
u8 *args[] = {
S("--static").s, S("--cflags").s, S("--libs").s, S("afl").s,
};
iz cap = 1<<16;
arena perm = {0};
perm.beg = malloc(cap);
perm.end = perm.beg + cap;
os ctx = {0};
ctx.pcfile.s = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
ctx.pcfile.len = __AFL_FUZZ_TESTCASE_LEN;
config conf = {0};
conf.perm = perm;
conf.perm.ctx = &ctx;
conf.args = args;
conf.nargs = countof(args);
conf.fixedpath = S("/usr/lib/pkgconfig");
if (!setjmp(ctx.ret)) {
uconfig(&conf);
}
}
}
u-config-0.34.0/main_linux_amd64.c 0000664 0000000 0000000 00000002043 15002525747 0016615 0 ustar 00root root 0000000 0000000 // libc-free platform layer for x86-64 Linux
// This is free and unencumbered software released into the public domain.
#include "src/u-config.c"
enum {
SYS_close = 3,
SYS_exit = 60,
SYS_open = 2,
SYS_read = 0,
SYS_write = 1,
SYS_getdents64 = 217,
};
#include "src/linux_noarch.c"
asm (
" .globl _start\n"
"_start: mov %rsp, %rdi\n"
" call entrypoint\n"
);
static long syscall1(long n, long a)
{
long r;
asm volatile (
"syscall"
: "=a"(r)
: "a"(n), "D"(a)
: "rcx", "r11", "memory"
);
return r;
}
static long syscall2(long n, long a, long b)
{
long r;
asm volatile (
"syscall"
: "=a"(r)
: "a"(n), "D"(a), "S"(b)
: "rcx", "r11", "memory"
);
return r;
}
static long syscall3(long n, long a, long b, long c)
{
long r;
asm volatile (
"syscall"
: "=a"(r)
: "a"(n), "D"(a), "S"(b), "d"(c)
: "rcx", "r11", "memory"
);
return r;
}
u-config-0.34.0/main_linux_i686.c 0000664 0000000 0000000 00000002046 15002525747 0016401 0 ustar 00root root 0000000 0000000 // libc-free platform layer for x86-32 Linux
// This is free and unencumbered software released into the public domain.
#include "src/u-config.c"
enum {
SYS_close = 6,
SYS_exit = 1,
SYS_open = 5,
SYS_read = 3,
SYS_write = 4,
SYS_getdents64 = 220,
};
#include "src/linux_noarch.c"
asm (
" .globl _start\n"
"_start: mov %esp, %eax\n"
" sub $12, %esp\n"
" push %eax\n"
" call entrypoint\n"
);
static long syscall1(long n, long a)
{
long r;
asm volatile (
"int $0x80"
: "=a"(r)
: "a"(n), "b"(a)
: "memory"
);
return r;
}
static long syscall2(long n, long a, long b)
{
long r;
asm volatile (
"int $0x80"
: "=a"(r)
: "a"(n), "b"(a), "c"(b)
);
return r;
}
static long syscall3(long n, long a, long b, long c)
{
long r;
asm volatile (
"int $0x80"
: "=a"(r)
: "a"(n), "b"(a), "c"(b), "d"(c)
: "memory"
);
return r;
}
u-config-0.34.0/main_msvc.c 0000664 0000000 0000000 00000000605 15002525747 0015435 0 ustar 00root root 0000000 0000000 // MSVC Win32 platform layer for u-config
// $ cl /GS- /O2 /Os /Fe:pkg-config.exe main_msvc.c
// This is free and unencumbered software released into the public domain.
#pragma comment(linker, "/subsystem:console")
#pragma comment(lib, "libvcruntime.lib")
#pragma comment(lib, "kernel32.lib")
#define __builtin_unreachable() __assume(0)
#define __attribute(x)
#include "main_windows.c"
u-config-0.34.0/main_posix.c 0000664 0000000 0000000 00000010421 15002525747 0015624 0 ustar 00root root 0000000 0000000 // POSIX platform layer for u-config
// This is free and unencumbered software released into the public domain.
#include
#include
#include
#include
#include "src/u-config.c"
#ifndef PKG_CONFIG_SYSTEM_INCLUDE_PATH
# define PKG_CONFIG_SYSTEM_INCLUDE_PATH "/usr/include"
#endif
#ifndef PKG_CONFIG_SYSTEM_LIBRARY_PATH
# define PKG_CONFIG_SYSTEM_LIBRARY_PATH "/lib:/usr/lib"
#endif
#ifndef PKG_CONFIG_PREFIX
# define PKG_CONFIG_PREFIX "/usr"
#endif
static const char pkg_config_path[] =
#ifdef PKG_CONFIG_PATH
PKG_CONFIG_PATH ":"
#endif
#ifdef PKG_CONFIG_LIBDIR
PKG_CONFIG_LIBDIR
#else
PKG_CONFIG_PREFIX "/local/lib/pkgconfig:"
PKG_CONFIG_PREFIX "/local/share/pkgconfig:"
PKG_CONFIG_PREFIX "/lib/pkgconfig:"
PKG_CONFIG_PREFIX "/share/pkgconfig"
#endif
;
static void os_fail(os *ctx)
{
(void)ctx;
_exit(1);
}
static void os_write(os *ctx, i32 fd, s8 s)
{
(void)ctx;
assert(fd==1 || fd==2);
while (s.len) {
ssize_t r = write(fd, s.s, (size_t)s.len);
if (r < 0) {
_exit(1);
}
s = cuthead(s, (iz)r);
}
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
(void)ctx;
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
filemap r = {0};
int fd = open((char *)path.s, 0);
if (fd < 0) {
r.status = filemap_NOTFOUND;
return r;
}
r.data.s = (u8 *)perm->beg;
iz cap = perm->end - perm->beg;
while (r.data.len < cap) {
u8 *dst = r.data.s + r.data.len;
ssize_t len = read(fd, dst, (size_t)(cap-r.data.len));
if (len < 1) {
break;
}
r.data.len += len;
}
close(fd);
if (r.data.len == cap) {
// If it filled all available space, assume the file is too large
r.status = filemap_READERR;
return r;
}
perm->beg += r.data.len;
r.status = filemap_OK;
return r;
}
static b32 endswith_(s8 s, s8 suffix)
{
return s.len>=suffix.len && s8equals(taketail(s, suffix.len), suffix);
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
(void)ctx;
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
// NOTE: will allocate while holding this handle
DIR *handle = opendir((char *)path.s);
if (!handle) {
return 0;
}
s8list files = {0};
for (struct dirent *d; (d = readdir(handle));) {
s8 name = s8fromcstr((u8 *)d->d_name);
if (endswith_(name, S(".pc"))) {
s8 copy = news8(a, name.len);
s8copy(copy, name);
append(&files, copy, a);
}
}
closedir(handle);
return files.head;
}
static arena newarena_(void)
{
iz cap = (iz)1<<22;
arena a = {0};
a.beg = malloc((size_t)cap);
if (!a.beg) {
a.beg = (byte *)16; // aligned, non-null, zero-size arena
cap = 0;
}
a.end = a.beg + cap;
return a;
}
static config *newconfig_(void)
{
arena perm = newarena_();
config *conf = new(&perm, config, 1);
conf->perm = perm;
conf->haslisting = 1;
return conf;
}
static s8 s8getenv_(char *k)
{
return s8fromcstr((u8 *)getenv(k));
}
int main(int argc, char **argv)
{
config *conf = newconfig_();
conf->delim = ':';
if (argc) {
argc--;
argv++;
}
conf->args = (u8 **)argv;
conf->nargs = argc;
conf->pc_path = S(pkg_config_path);
conf->pc_sysincpath = S(PKG_CONFIG_SYSTEM_INCLUDE_PATH);
conf->pc_syslibpath = S(PKG_CONFIG_SYSTEM_LIBRARY_PATH);
conf->envpath = s8getenv_("PKG_CONFIG_PATH");
conf->fixedpath = s8getenv_("PKG_CONFIG_LIBDIR");
if (!conf->fixedpath.s) {
conf->fixedpath = S(pkg_config_path);
}
conf->sys_incpath = s8getenv_("PKG_CONFIG_SYSTEM_INCLUDE_PATH");
if (!conf->sys_incpath.s) {
conf->sys_incpath = S(PKG_CONFIG_SYSTEM_INCLUDE_PATH);
}
conf->sys_libpath = s8getenv_("PKG_CONFIG_SYSTEM_LIBRARY_PATH");
if (!conf->sys_libpath.s) {
conf->sys_libpath = S(PKG_CONFIG_SYSTEM_LIBRARY_PATH);
}
conf->top_builddir = s8getenv_("PKG_CONFIG_TOP_BUILD_DIR");
conf->print_sysinc = s8getenv_("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS");
conf->print_syslib = s8getenv_("PKG_CONFIG_ALLOW_SYSTEM_LIBS");
uconfig(conf);
return 0;
}
u-config-0.34.0/main_test.c 0000664 0000000 0000000 00000066330 15002525747 0015453 0 ustar 00root root 0000000 0000000 // Test suite for u-config
// On success prints "all tests pass" (for humans) and exits with a zero
// status (for scripts). Attach a debugger to examine failures in detail.
// This is free and unencumbered software released into the public domain.
#ifndef __GNUC__
# define __builtin_unreachable() *(volatile int *)0 = 0
# define __builtin_trap() *(volatile int *)0 = 0
# define __attribute(x)
#endif
#include "src/u-config.c"
#include
#include
#include
#include
#define E S("")
#define SHOULDPASS \
for (i32 r = setjmp(conf.perm.ctx->exit); \
!r || (r>0 && (__builtin_trap(), 0)); \
r = -1)
#define SHOULDFAIL \
for (i32 r = setjmp(conf.perm.ctx->exit); !r; __builtin_trap())
#define PCHDR "Name:\n" "Version:\n" "Description:\n"
#define EXPECT(w) \
if (!s8equals(conf.perm.ctx->output, S(w))) { \
printf("EXPECT: %s", w); \
printf("OUTPUT: %.*s", \
(int)conf.perm.ctx->output.len, conf.perm.ctx->output.s); \
fflush(stdout); \
__builtin_trap(); \
}
#define MATCH(w) \
if (s8find_(conf.perm.ctx->output, S(w)) == conf.perm.ctx->output.len) { \
printf("MATCH: %s\n", w); \
printf("OUTPUT: %.*s", \
(int)conf.perm.ctx->output.len, conf.perm.ctx->output.s); \
fflush(stdout); \
__builtin_trap(); \
}
struct os {
jmp_buf exit;
arena perm;
s8 outbuf;
s8 output;
s8 outavail;
env *filesystem;
b32 active;
};
static void os_fail(os *ctx)
{
assert(ctx->active);
ctx->active = 0;
longjmp(ctx->exit, 1);
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
(void)perm;
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
path.len--; // trim null terminator
filemap r = {0};
s8 *data = insert(&ctx->filesystem, path, 0);
if (!data) {
r.status = filemap_NOTFOUND;
return r;
}
r.data = *data;
r.status = filemap_OK;
return r;
}
static iz s8compare_(s8 a, s8 b)
{
iz len = a.lennext) {
return head;
}
iz len = 0;
s8node *tail = head;
s8node *last = head;
for (s8node *n = head; n; n = n->next, len++) {
if (len & 1) {
last = tail;
tail = tail->next;
}
}
last->next = 0;
head = s8sort_(head);
tail = s8sort_(tail);
s8node *rhead = 0;
s8node **rtail = &rhead;
while (head && tail) {
if (s8compare_(head->str, tail->str) < 1) {
*rtail = head;
head = head->next;
} else {
*rtail = tail;
tail = tail->next;
}
rtail = &(*rtail)->next;
}
*rtail = head ? head : tail;
return rhead;
}
static s8list os_listing_(s8list r, env *fs, arena *a, s8 path)
{
if (fs) {
if (startswith(fs->name, path)) {
append(&r, cuthead(fs->name, path.len+1), a);
}
for (i32 i = 0; i < 4; i++) {
r = os_listing_(r, fs->child[i], a, path);
}
}
return r;
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
s8list r = {0};
r = os_listing_(r, ctx->filesystem, a, cuttail(path, 1));
return s8sort_(r.head); // sort for determinism
}
static void os_write(os *ctx, i32 fd, s8 s)
{
assert(fd==1 || fd==2);
assert(ctx->outavail.len >= s.len);
ctx->outavail = s8copy(ctx->outavail, s);
ctx->output.len += s.len;
}
// Return needle offset, or the length of haystack on no match.
static iz s8find_(s8 haystack, s8 needle)
{
u32 match = 0;
for (iz i = 0; i < needle.len; i++) {
match = match*257u + needle.s[i];
}
u32 f = 1;
u32 x = 257;
for (iz n = needle.len-1; n>0; n /= 2) {
f *= n&1 ? x : 1;
x *= x;
}
iz i = 0;
u32 hash = 0;
for (; ioutbuf = news8(&a, 1<<10);
config conf = {0};
conf.delim = ':';
conf.sys_incpath = S("/usr/include");
conf.sys_libpath = S("/lib:/usr/lib");
conf.fixedpath = S("/usr/lib/pkgconfig:/usr/share/pkgconfig");
conf.perm = a;
conf.perm.ctx = ctx;
conf.haslisting = 1;
return conf;
}
static void newfile_(config *conf, s8 path, s8 contents)
{
os *ctx = conf->perm.ctx;
*insert(&ctx->filesystem, path, &conf->perm) = contents;
}
static void run(config conf, ...)
{
va_list ap;
va_start(ap, conf);
for (conf.nargs = 0;; conf.nargs++) {
s8 arg = va_arg(ap, s8);
if (!arg.len) {
break;
}
}
va_end(ap);
conf.args = new(&conf.perm, u8 *, conf.nargs);
va_start(ap, conf);
for (iz i = 0; i < conf.nargs; i++) {
conf.args[i] = va_arg(ap, s8).s;
}
va_end(ap);
os *ctx = conf.perm.ctx;
ctx->output = takehead(ctx->outbuf, 0);
ctx->outavail = ctx->outbuf;
fillbytes(conf.perm.beg, 0x55, conf.perm.end-conf.perm.beg);
ctx->active = 1;
uconfig(&conf);
assert(ctx->active);
ctx->active = 0;
}
static void test_noargs(arena a)
{
// NOTE: this is mainly a sanity check of the test system itself
config conf = newtest_(a, S("no arguments"));
SHOULDFAIL {
run(conf, E);
}
}
static void test_dashdash(arena a)
{
config conf = newtest_(a, S("handle -- argument"));
newfile_(&conf, S("/usr/lib/pkgconfig/--foo.pc"), S(
PCHDR
"Cflags: -Dfoo\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/--.pc"), S(
PCHDR
"Cflags: -Ddashdash\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("--"), S("--foo"), S("--"), E);
}
EXPECT("-Dfoo -Ddashdash\n");
}
static void test_modversion(arena a)
{
config conf = newtest_(a, S("--modversion"));
newfile_(&conf, S("/usr/lib/pkgconfig/direct.pc"), S(
"Name:\n"
"Version: 1.2.3\n"
"Description:\n"
"Requires: req"
));
newfile_(&conf, S("/usr/share/pkgconfig/indirect.pc"), S(
"major = 12\n"
"minor = 345\n"
"patch = 6789\n"
"version = ${major}.${minor}.${patch}\n"
"Name:\n"
"Version: ${version}\n"
"Description:\n"
));
newfile_(&conf, S("/usr/share/pkgconfig/req.pc"), S(
"version = 420.69.1337\n"
"Name:\n"
"Version: ${version}\n"
"Description:\n"
));
SHOULDPASS {
run(conf, S("--modversion"), S("direct"), S("indirect"), E);
EXPECT("1.2.3\n12.345.6789\n");
}
SHOULDPASS {
// One package is listed twice, first by its name and discovered
// by searching the path, and second by directly by its path. It
// must be recognized internally as the same package and so only
// print one version.
run(conf, S("--modversion"), S("direct"),
S("/usr/lib/pkgconfig/direct.pc"), E);
EXPECT("1.2.3\n");
}
}
static void test_versioncheck(arena a)
{
config conf = newtest_(a, S("version checks"));
newfile_(&conf, S("/usr/lib/pkgconfig/test.pc"), S(
"Name:\n"
"Version: 1.2.3\n"
"Description:\n"
));
SHOULDPASS {
run(conf, S("--modversion"), S("test = 1.2.3"), E);
}
SHOULDPASS {
run(conf, S("--modversion"), S("test "), S("= 1.2.3"), E);
}
SHOULDPASS {
run(conf, S("--modversion"), S("test "), S("="), S(" 1.2.3"), E);
}
SHOULDPASS {
run(conf, S("--modversion"), S("test ="), S("1.2.3"), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test < 1.2.3"), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test <= 1.2.2"), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test > 1.2.3"), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test >= 1.2.4"), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test ="), E);
}
SHOULDFAIL {
run(conf, S("--modversion"), S("test"), S("="), E);
}
}
static void test_versionorder(arena a)
{
// Scenario: liba depends on libc and libb
// Expect: modversion order is unaffected
config conf = newtest_(a, S("library version ordering"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
"Name: \n"
"Description: \n"
"Version: 1\n"
"Requires: c b\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
"Name: \n"
"Description: \n"
"Version: 2\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/c.pc"), S(
"Name: \n"
"Description: \n"
"Version: 3\n"
));
SHOULDPASS {
run(conf, S("--modversion"), S("a"), S("b"), S("c"), E);
}
EXPECT("1\n2\n3\n");
}
static void test_overrides(arena a)
{
config conf = newtest_(a, S("--{atleast,exact,max}-version"));
newfile_(&conf, S("/usr/lib/pkgconfig/t.pc"), S(
"Name:\n"
"Version: 1\n"
"Description:\n"
));
SHOULDPASS {
run(conf, S("--atleast-version=0"), S("t > 1"), E);
}
SHOULDPASS {
run(conf, S("--exact-version=1"), S("t > 1"), E);
}
SHOULDPASS {
run(conf, S("--max-version=2"), S("t > 1"), E);
}
SHOULDFAIL {
run(conf, S("--atleast-version=2"), S("t = 1"), E);
}
SHOULDFAIL {
run(conf, S("--exact-version=2"), S("t = 1"), E);
}
SHOULDFAIL {
run(conf, S("--max-version=0"), S("t = 1"), E);
}
SHOULDFAIL {
run(conf, S("--atleast-version=2"), S("t"), E);
}
SHOULDFAIL {
run(conf, S("--exact-version=2"), S("t"), E);
}
SHOULDFAIL {
run(conf, S("--max-version=0"), S("t"), E);
}
}
static void test_maximum_traverse_depth(arena a)
{
config conf = newtest_(a, S("--maximum-traverse-depth"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Requires: b\n"
"Cflags: -Da\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Requires: c\n"
"Cflags: -Db\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/c.pc"), S(
PCHDR
"Cflags: -Dc\n"
));
SHOULDPASS {
run(conf, S("--maximum-traverse-depth=1"), S("--cflags"), S("a"), E);
}
EXPECT("-Da\n");
SHOULDPASS {
run(conf, S("--maximum-traverse-depth=2"), S("--cflags"), S("a"), E);
}
EXPECT("-Da -Db\n");
SHOULDPASS {
run(conf, S("--maximum-traverse-depth=3"), S("--cflags"), S("a"), E);
}
EXPECT("-Da -Db -Dc\n");
}
static void test_private_cflags(arena a)
{
// Scenario: a has private cflags
// Expect: --cflags should not output it unless --static is also given
config conf = newtest_(a, S("private cflags"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Cflags: -DA_PUB\n"
"Cflags.private: -DA_PRIV\n"
"Libs: -la\n"
));
// Scenario: b has private cflags and so does its dependencies
// Expect: only output private flags if --static is given
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Cflags: -DB_PUB\n"
"Cflags.private: -DB_PRIV\n"
"Libs: -lb\n"
"Requires: c\n"
"Requires.private: d\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/c.pc"), S(
PCHDR
"Cflags: -DC_PUB\n"
"Cflags.private: -DC_PRIV\n"
"Libs: -lc\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/d.pc"), S(
PCHDR
"Cflags: -DD_PUB\n"
"Cflags.private: -DD_PRIV\n"
"Libs: -ld\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("a"), E);
}
EXPECT("-DA_PUB\n");
SHOULDPASS {
run(conf, S("--static"), S("--cflags"), S("a"), E);
}
EXPECT("-DA_PUB -DA_PRIV\n");
SHOULDPASS {
run(conf, S("--libs"), S("--static"), S("a"), E);
}
EXPECT("-la\n");
SHOULDPASS {
run(conf, S("--cflags"), S("b"), E);
}
EXPECT("-DB_PUB -DC_PUB -DD_PUB\n");
SHOULDPASS {
run(conf, S("--cflags"), S("--static"), S("b"), E);
}
EXPECT("-DB_PUB -DB_PRIV -DC_PUB -DC_PRIV -DD_PUB -DD_PRIV\n");
}
static void test_private_transitive(arena a)
{
// Scenario: a privately requires b which publicly requires c
// Expect: --libs should not include c without --static
config conf = newtest_(a, S("private transitive"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Requires: x\n"
"Requires.private: b\n"
"Libs: -la\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/x.pc"), S(
PCHDR
"Libs: -lx\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Requires: c\n"
"Libs: -lb\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/c.pc"), S(
PCHDR
"Requires.private: b\n"
"Libs: -lc\n"
));
SHOULDPASS {
run(conf, S("--libs"), S("a"), E);
}
EXPECT("-la -lx\n");
SHOULDPASS {
run(conf, S("--libs"), S("--static"), S("a"), E);
}
EXPECT("-la -lx -lb -lc\n");
}
static void test_revealed_transitive(arena a)
{
// Scenario: a privately requires b, which requires x
// Expect: "--libs a" lists only a, "--libs a b" reveals x
//
// The trouble is that x is initially loaded private. However, when
// loading b it should become public, and so must be revisited in
// traversal and marked as such.
config conf = newtest_(a, S("revealed transitive"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Requires.private: b\n"
"Libs: -la\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Requires: x\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/x.pc"), S(
PCHDR
"Libs: -lx\n"
));
SHOULDPASS {
run(conf, S("--libs"), S("a"), E);
}
EXPECT("-la\n");
SHOULDPASS {
run(conf, S("--libs"), S("a"), S("b"), E);
}
EXPECT("-la -lx\n");
}
static void test_syspaths(arena a)
{
config conf = newtest_(a, S("exclude syspaths"));
newfile_(&conf, S("/usr/lib/pkgconfig/example.pc"), S(
PCHDR
"prefix=/usr\n"
"Cflags: -DEXAMPLE -I${prefix}/include\n"
"Libs: -L${prefix}/lib -lexample\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("example"), E);
}
EXPECT("-DEXAMPLE -lexample\n");
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("example"),
S("--keep-system-cflags"), E);
}
EXPECT("-DEXAMPLE -I/usr/include -lexample\n");
SHOULDPASS {
config copy = conf;
copy.print_sysinc = S("");
run(copy, S("--cflags"), S("--libs"), S("example"), E);
}
EXPECT("-DEXAMPLE -I/usr/include -lexample\n");
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("example"),
S("--keep-system-libs"), E);
}
EXPECT("-DEXAMPLE -L/usr/lib -lexample\n");
SHOULDPASS {
config copy = conf;
copy.print_syslib = S("");
run(copy, S("--cflags"), S("--libs"), S("example"), E);
}
EXPECT("-DEXAMPLE -L/usr/lib -lexample\n");
}
static void test_libsorder(arena a)
{
// Scenario: two packages link a common library
// Expect: the common library is listed after both, other flags
// maintain their first-seen position and de-duplicate the rest
config conf = newtest_(a, S("library ordering"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Cflags: -DA -DGL\n"
"Libs: -L/opt/lib -pthread -mwindows -la -lopengl32\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Cflags: -DB -DGL\n"
"Libs: -L/opt/lib -pthread -mwindows -lb -lopengl32\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("a b"), E);
}
EXPECT("-DA -DGL -DB -L/opt/lib -pthread -mwindows -la -lb -lopengl32\n");
}
static void test_staticorder(arena a)
{
// Scenario: liba depends on libc and libb, libb depends on libc
// Expect: libc is listed last
config conf = newtest_(a, S("static library ordering"));
newfile_(&conf, S("/usr/lib/pkgconfig/a.pc"), S(
PCHDR
"Requires.private: c b\n"
"Libs: -la\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/b.pc"), S(
PCHDR
"Requires.private: c\n"
"Libs: -lb\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/c.pc"), S(
PCHDR
"Libs: -lc\n"
));
SHOULDPASS {
run(conf, S("--static"), S("--libs"), S("a"), E);
}
EXPECT("-la -lb -lc\n");
}
static void test_windows(arena a)
{
// Tests the ';' delimiter, that the prefix is overridden, and that
// prefixes containing spaces are properly quoted. The fixed path
// would be Win32 platform's fixed path if the binary was located in
// "$HOME/bin".
config conf = newtest_(a, S("windows"));
conf.fixedpath = S(
"C:/Documents and Settings/John Falstaff/lib/pkgconfig;"
"C:/Documents and Settings/John Falstaff/share/pkgconfig"
);
conf.envpath = S(
"C:/Program Files/Example/lib/pkgconfig;"
"C:/Program Files/SDL2/x86_64-w64-mingw32/lib/pkgconfig"
);
conf.sys_incpath = S(
"C:/w64devkit/x86_64-w64-mingw32/include;"
"C:/Documents and Settings/John Falstaff/include"
);
conf.sys_libpath = S(
"C:/w64devkit/x86_64-w64-mingw32/lib;"
"C:/Documents and Settings/John Falstaff/lib"
);
conf.define_prefix = 1;
conf.delim = ';';
newfile_(&conf, S(
"C:/Documents and Settings/John Falstaff/lib/pkgconfig/example.pc"
), S(
PCHDR
"prefix=/usr\n"
"libdir=${prefix}/lib\n"
"includedir=${prefix}/include\n"
"Libs: -L${libdir} -lexample\n"
"Cflags: -I\"${includedir}\"\n"
));
newfile_(&conf, S(
"C:/Program Files/SDL2/x86_64-w64-mingw32/lib/pkgconfig/sdl2.pc"
), S(
"prefix=/opt/local/x86_64-w64-mingw32\n"
"exec_prefix=${prefix}\n"
"libdir=${exec_prefix}/lib\n"
"includedir=${prefix}/include\n"
"Name: sdl2\n"
"Description: Simple DirectMedia Layer\n"
"Version: 2.26.2\n"
"Libs: -L${libdir} -lmingw32 -lSDL2main -lSDL2 -mwindows\n"
"Cflags: -I${includedir} -I${includedir}/SDL2 -Dmain=SDL_main\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("example"), E);
}
EXPECT("-lexample\n");
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("sdl2"), E);
}
EXPECT(
"-IC:/Program\\ Files/SDL2/x86_64-w64-mingw32/include "
"-IC:/Program\\ Files/SDL2/x86_64-w64-mingw32/include/SDL2 "
"-Dmain=SDL_main "
"-LC:/Program\\ Files/SDL2/x86_64-w64-mingw32/lib "
"-lmingw32 -lSDL2main -lSDL2 -mwindows\n"
);
}
static void test_parens(arena a)
{
// Test if that paths allow parenthesis, but also that parenthesis
// otherwise still work as meta characters.
config conf = newtest_(a, S("parens"));
conf.fixedpath = S(
"C:/Program Files (x86)/Contoso/lib/pkgconfig"
);
conf.define_prefix = 1;
conf.delim = ';';
newfile_(&conf, S(
"C:/Program Files (x86)/Contoso/lib/pkgconfig/example.pc"
), S(
PCHDR
"prefix=/usr/local\n"
"Cflags: -I${pc_top_builddir}/include -I${prefix}/include\n"
"Libs: -L\"${pc_top_builddir}/lib\"\n"
));
SHOULDPASS {
run(conf, S("--cflags"), S("--libs"), S("example"), E);
}
EXPECT(
"-I$(top_builddir)/include "
"-IC:/Program\\ Files\\ \\(x86\\)/Contoso/include "
"-L$(top_builddir)/lib\n"
);
conf.top_builddir = S("U:/falstaffj$/Henry IV (Part 1)");
SHOULDPASS {
run(conf, S("--libs"), S("example"), E);
}
EXPECT(
"-LU:/falstaffj\\$/Henry\\ IV\\ \\(Part\\ 1\\)/lib\n"
);
}
static void test_listing(arena a)
{
config conf = newtest_(a, S("package listing"));
newfile_(&conf, S("/usr/lib/pkgconfig/alpha.pc"), S(
"Name: Alpha\n"
"Version: 1.23\n"
"Description: the first package\n"
));
newfile_(&conf, S("/usr/share/pkgconfig/beta.pc"), S(
"Name: Beta\n"
"Version: 4.56\n"
"Description: the second package\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/omega.pc"), S(
"Name: Omega\n"
"Version: 7.89\n"
"Description: the last package\n"
));
newfile_(&conf, S("/usr/share/pkgconfig/zeta.pc"), S(
PCHDR
));
newfile_(&conf, S("/tmp/invisible.pc"), S(
PCHDR
));
SHOULDPASS {
run(conf, S("--list-package-names"), E);
}
EXPECT( // NOTE: grouped by directory; pkg-config doesn't sort
"alpha\nomega\nbeta\nzeta\n"
);
SHOULDPASS {
run(conf, S("--list-all"), E);
}
EXPECT(
"alpha Alpha - the first package\n"
"omega Omega - the last package\n"
"beta Beta - the second package\n"
"zeta - \n"
);
}
static void test_error_messages(arena a)
{
// Check that error messages mention important information
config conf = newtest_(a, S("error messages"));
newfile_(&conf, S("/usr/lib/pkgconfig/badpkg.pc"), S(
PCHDR
"Requires: < 1\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/goodpkg.pc"), S(
PCHDR
"Requires: badpkg\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/missingversion.pc"), S(
PCHDR
"Requires: pkg-config >\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/toodeep.pc"), S(
PCHDR
"Requires: ${x}\n"
"x = x${x}\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/undefinedvar.pc"), S(
PCHDR
"Requires: ${whatisthis}\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/dupvar.pc"), S(
PCHDR
"toomanydefs = a\n"
"toomanydefs = b\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/missingfield.pc"), S(
"Name:\n"
"Version:\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/versionedpkg.pc"), S(
"Name:\n"
"Version: 2\n"
"Description:\n"
));
newfile_(&conf, S("/usr/lib/pkgconfig/badquotes.pc"), S(
PCHDR
"Cflags: ${x}\n"
"x = -I\"\n"
));
SHOULDFAIL { // should be silent
run(conf, S("--exists"), S("nonexistingpkg"), E);
}
EXPECT("");
SHOULDFAIL {
run(conf, S("--atleast-version"), S("9"), S("nonexistingpkg"), E);
}
EXPECT("");
SHOULDFAIL {
run(conf, S("--cflags"), S("nonexistingpkg"), E);
}
MATCH("nonexistingpkg");
SHOULDFAIL { // direct
run(conf, S("--cflags"), S("badpkg"), E);
}
MATCH("badpkg");
SHOULDFAIL { // indirect
run(conf, S("--cflags"), S("goodpkg"), E);
}
MATCH("badpkg");
SHOULDFAIL {
run(conf, S("--cflags"), S("missingversion"), E);
}
MATCH("missingversion");
SHOULDFAIL {
run(conf, S("--cflags"), S("toodeep"), E);
}
MATCH("toodeep");
SHOULDFAIL {
run(conf, S("--cflags"), S("undefinedvar"), E);
}
MATCH("undefinedvar");
MATCH("whatisthis");
SHOULDFAIL {
run(conf, S("--cflags"), S("dupvar"), E);
}
MATCH("dupvar");
MATCH("toomanydefs");
SHOULDFAIL {
run(conf, S("--cflags"), S("missingfield"), E);
}
MATCH("missingfield");
MATCH("Description");
SHOULDFAIL {
run(conf, S("--cflags"), S("versionedpkg = 1"), E);
}
MATCH("versionedpkg");
MATCH("'1'");
MATCH("'2'");
SHOULDFAIL {
run(conf, S("--cflags"), S("badquotes"), E);
}
MATCH("unmatched");
MATCH("badquotes");
}
static void printi32_(u8buf *out, i32 x)
{
u8 buf[32];
u8 *e = buf + countof(buf);
u8 *p = e;
do {
*--p = (u8)(x%10) + '0';
} while (x /= 10);
prints8(out, s8span(p, e));
}
static void test_manyvars(arena a)
{
// Stresses the hash-trie-backed package environment
config conf = newtest_(a, S("many variables"));
newfile_(&conf, S("manyvars.pc"), S("")); // allocate empty file
i32 nvars = 10000;
for (i32 i = 0; i < nvars; i += 197) {
config temp = conf;
u8 prefix = 'a' + (u8)(i%26);
// Write a fresh .pc file into the virtual "manyvars.pc" with a
// rotated variable order and prefix to perturb the package Env.
u8buf pc = newmembuf(&temp.perm);
prints8(&pc, S(PCHDR));
for (i32 v = 0; v < nvars; v++) {
i32 vnum = (v + i) % nvars;
printu8(&pc, prefix);
printi32_(&pc, vnum);
printu8(&pc, '=');
printu8(&pc, 'A' + (u8)(vnum%26));
printu8(&pc, '\n');
}
newfile_(&temp, S("manyvars.pc"), finalize(&pc)); // overwrite
// Probe a variable to test the environment
u8buf mem = newmembuf(&temp.perm);
printu8(&mem, prefix);
printi32_(&mem, i);
printu8(&mem, 0);
s8 var = finalize(&mem);
SHOULDPASS {
run(temp, S("manyvars.pc"), S("--variable"), var, E);
}
u8 expect[] = {'A' + (u8)(i%26), '\n', 0};
EXPECT(expect);
}
}
static void test_lol(arena a)
{
config conf = newtest_(a, S("a billion laughs"));
newfile_(&conf, S("lol.pc"), S(
"v9=lol\n"
"v8=${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9}${v9}\n"
"v7=${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8}${v8}\n"
"v6=${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7}${v7}\n"
"v5=${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6}${v6}\n"
"v4=${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5}${v5}\n"
"v3=${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4}${v4}\n"
"v2=${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3}${v3}\n"
"v1=${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2}${v2}\n"
"v0=${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1}${v1}\n"
"Name:\n"
"Version: ${v0}\n"
"Description:\n"
));
SHOULDFAIL {
run(conf, S("--modversion"), S("lol.pc"), E);
}
MATCH("out of memory");
}
static arena newarena_(iz cap)
{
arena arena = {0};
arena.beg = malloc((size_t)cap);
if (!arena.beg) {
__builtin_trap();
}
arena.end = arena.beg + cap;
return arena;
}
int main(void)
{
arena a = newarena_(1<<21);
test_noargs(a);
test_dashdash(a);
test_modversion(a);
test_versioncheck(a);
test_versionorder(a);
test_overrides(a);
test_maximum_traverse_depth(a);
test_private_cflags(a);
test_private_transitive(a);
test_revealed_transitive(a);
test_syspaths(a);
test_libsorder(a);
test_staticorder(a);
test_windows(a);
test_parens(a);
test_listing(a);
test_error_messages(a);
test_manyvars(a);
test_lol(a);
puts("all tests pass");
return 0;
}
u-config-0.34.0/main_wasm.c 0000664 0000000 0000000 00000016037 15002525747 0015442 0 ustar 00root root 0000000 0000000 #include "src/u-config.c"
typedef long long i64;
enum {
WASI_FD_READ = 1 << 1,
WASI_FD_READDIR = 1 << 14,
WASI_O_DIRECTORY = 1 << 1,
};
#define WASI(s) \
__attribute((import_module("wasi_snapshot_preview1"), import_name(s)))
WASI("args_get") i32 args_get(u8 **, u8 *);
WASI("args_sizes_get") i32 args_sizes_get(i32 *, iz *);
WASI("environ_get") i32 environ_get(u8 **, u8 *);
WASI("environ_sizes_get") i32 environ_sizes_get(i32 *, iz *);
WASI("fd_close") i32 fd_close(i32);
WASI("fd_prestat_dir_name") i32 fd_prestat_dir_name(i32, u8 *, iz);
WASI("fd_prestat_get") i32 fd_prestat_get(i32, iz *);
WASI("fd_read") i32 fd_read(i32, s8 *, iz, iz *);
WASI("fd_readdir") i32 fd_readdir(i32, u8 *, iz, i64, iz *);
WASI("fd_write") i32 fd_write(i32, s8 *, iz, iz *);
WASI("path_open") i32 path_open(i32,i32,u8*,iz,i32,i64,i64,i32,i32*);
WASI("proc_exit") void proc_exit(i32);
typedef struct dirfd dirfd;
struct dirfd {
dirfd *next;
s8 path;
i32 fd;
};
struct os {
dirfd *dirs;
};
static b32 endswith_(s8 s, s8 suffix)
{
return s.len>=suffix.len && s8equals(taketail(s, suffix.len), suffix);
}
static dirfd *find_preopens(arena *a)
{
dirfd *head = 0;
dirfd **tail = &head;
for (i32 fd = 3;; fd++) {
iz stat[2];
if (fd_prestat_get(fd, stat)) {
return head;
}
s8 path = {0};
path.len = stat[1]+1;
path.s = new(a, u8, path.len);
if (fd_prestat_dir_name(fd, path.s, stat[1])) {
return head;
}
path.s[stat[1]] = '/';
// Force exactly one trailing slash
while (endswith_(path, S("//"))) {
path = cuttail(path, 1);
}
*tail = new(a, dirfd, 1);
(*tail)->path = path;
(*tail)->fd = fd;
tail = &(*tail)->next;
}
}
typedef struct {
s8 relpath;
i32 fd;
b32 ok;
} relpath;
static relpath find_dirfd(os *ctx, s8 path)
{
relpath r = {0};
for (dirfd *d = ctx->dirs; d; d = d->next) {
// Match without trailing slash
s8 dir = d->path;
while (endswith_(dir, S("/"))) {
dir = cuttail(dir, 1);
}
// TODO: parse path and match components
if (startswith(path, dir)) {
r.relpath = cuthead(path, dir.len);
// Remove leading slashes
while (startswith(r.relpath, S("/"))) {
r.relpath = cuthead(r.relpath, 1);
}
// Empty path really means current directory
if (!r.relpath.len) {
r.relpath = S(".");
}
r.fd = d->fd;
r.ok = 1;
return r;
}
}
return r;
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
filemap r = {0};
path = cuttail(path, 1); // remove null terminator
relpath rel = find_dirfd(ctx, path);
if (!rel.ok) {
r.status = filemap_NOTFOUND;
return r;
}
path = rel.relpath;
i32 fd = -1;
i32 err = path_open(
rel.fd, 0, path.s, path.len, 0, WASI_FD_READ, 0, 0, &fd
);
if (err) {
r.status = filemap_NOTFOUND;
return r;
}
iz cap = perm->end - perm->beg;
r.data.s = (u8 *)perm->beg;
r.data.len = cap;
err = fd_read(fd, &r.data, 1, &r.data.len);
fd_close(fd);
if (err || r.data.len==cap) {
r.status = filemap_READERR;
return r;
}
perm->beg += r.data.len;
r.status = filemap_OK;
return r;
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
s8list r = {0};
path = cuttail(path, 1); // remove null terminator
relpath rel = find_dirfd(ctx, path);
if (!rel.ok) {
return 0;
}
path = rel.relpath;
i32 fd = -1;
i32 err = path_open(
rel.fd, 0,
path.s, path.len,
WASI_O_DIRECTORY, WASI_FD_READDIR, 0, 0, &fd
);
if (err) {
return 0;
}
i64 cookie = 0;
i32 cap = 1<<14;
u8 *buf = new(a, u8, cap);
for (;;) {
iz len = 0;
err = fd_readdir(fd, buf, cap, cookie, &len);
if (err || !len) {
break;
}
for (iz off = 0; off < len;) {
struct {
i64 cookie;
i64 inode;
i32 len;
u8 type;
} entry;
// NOTE: fd_readdir() returns unaligned data (!!!)
__builtin_memcpy(&entry, buf+off, sizeof(entry));
off += sizeof(entry);
s8 name = {buf+off, entry.len};
off += entry.len;
if (off > len) {
break; // truncated
}
cookie = entry.cookie;
if (endswith_(name, S(".pc"))) {
s8 copy = news8(a, name.len);
s8copy(copy, name);
append(&r, copy, a);
}
}
}
fd_close(fd);
return r.head;
}
static void os_write(os *ctx, i32 fd, s8 data)
{
if (fd_write(fd, &data, 1, &data.len)) {
os_fail(ctx);
}
}
static void os_fail(os *ctx)
{
(void)ctx;
proc_exit(1);
__builtin_unreachable();
}
void _start(void)
{
os ctx = {0};
static byte heap[1<<22];
arena perm = {0};
perm.beg = heap;
perm.end = heap + sizeof(heap);
perm.ctx = &ctx;
ctx.dirs = find_preopens(&perm);
i32 argc = 0;
iz buflen = 0;
args_sizes_get(&argc, &buflen);
u8 **argv = new(&perm, u8 *, argc);
u8 *buf = new(&perm, u8, buflen);
args_get(argv, buf);
i32 envc = 0;
iz envlen = 0;
environ_sizes_get(&envc, &envlen);
u8 **envp = new(&perm, u8 *, envc);
u8 *env = new(&perm, u8, envlen);
environ_get(envp, env);
config conf = {0};
conf.perm = perm;
conf.args = argv + !!argc;
conf.nargs = argc - !!argc;
conf.pc_path = S("/usr/lib/pkgconfig:/usr/share/pkgconfig");
conf.fixedpath = conf.pc_path;
conf.pc_sysincpath = conf.sys_incpath = S("/usr/include");
conf.pc_syslibpath = conf.sys_libpath = S("/usr/lib");
conf.delim = ':';
conf.haslisting = 1;
for (i32 i = 0; i < envc; i++) {
cut c = s8cut(s8fromcstr(envp[i]), '=');
s8 name = c.head;
s8 value = c.tail;
if (s8equals(name, S("PKG_CONFIG_PATH"))) {
conf.envpath = value;
} else if (s8equals(name, S("PKG_CONFIG_LIBDIR"))) {
conf.fixedpath = value;
} else if (s8equals(name, S("PKG_CONFIG_TOP_BUILD_DIR"))) {
conf.top_builddir = value;
} else if (s8equals(name, S("PKG_CONFIG_SYSTEM_INCLUDE_PATH"))) {
conf.sys_incpath = value;
} else if (s8equals(name, S("PKG_CONFIG_SYSTEM_LIBRARY_PATH"))) {
conf.sys_libpath = value;
} else if (s8equals(name, S("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"))) {
conf.print_sysinc = value;
} else if (s8equals(name, S("PKG_CONFIG_ALLOW_SYSTEM_LIBS"))) {
conf.print_syslib = value;
}
}
uconfig(&conf);
}
u-config-0.34.0/main_windows.c 0000664 0000000 0000000 00000033520 15002525747 0016161 0 ustar 00root root 0000000 0000000 // Mingw-w64 Win32 platform layer for u-config
// $ cc -nostartfiles -o pkg-config main_windows.c
// This is free and unencumbered software released into the public domain.
#include "src/u-config.c"
#include "src/miniwin32.h"
#include "src/cmdline.c"
#ifndef PKG_CONFIG_PREFIX
# define PKG_CONFIG_PREFIX
#endif
// For communication with os_write()
struct os {
struct {
iptr h;
b32 isconsole;
b32 err;
} handles[3];
};
typedef struct {
c16 *s;
iz len;
} s16;
static s16 s16cuthead_(s16 s, iz off)
{
assert(off >= 0);
assert(off <= s.len);
s.s += off;
s.len -= off;
return s;
}
static arena newarena_(iz cap)
{
arena arena = {0};
arena.beg = VirtualAlloc(0, cap, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
if (!arena.beg) {
arena.beg = (byte *)16; // aligned, non-null, zero-size arena
cap = 0;
}
arena.end = arena.beg + cap;
return arena;
}
typedef i32 char32_t;
typedef char32_t c32;
enum {
REPLACEMENT_CHARACTER = 0xfffd
};
typedef struct {
s8 tail;
c32 rune;
} utf8;
static utf8 utf8decode_(s8 s)
{
assert(s.len);
utf8 r = {0};
switch (s.s[0]&0xf0) {
default : r.rune = s.s[0];
if (r.rune > 0x7f) break;
r.tail = cuthead(s, 1);
return r;
case 0xc0:
case 0xd0: if (s.len < 2) break;
if ((s.s[1]&0xc0) != 0x80) break;
r.rune = (i32)(s.s[0]&0x1f) << 6 |
(i32)(s.s[1]&0x3f) << 0;
if (r.rune < 0x80) break;
r.tail = cuthead(s, 2);
return r;
case 0xe0: if (s.len < 3) break;
if ((s.s[1]&0xc0) != 0x80) break;
if ((s.s[2]&0xc0) != 0x80) break;
r.rune = (i32)(s.s[0]&0x0f) << 12 |
(i32)(s.s[1]&0x3f) << 6 |
(i32)(s.s[2]&0x3f) << 0;
if (r.rune < 0x800) break;
if (r.rune>=0xd800 && r.rune<=0xdfff) break;
r.tail = cuthead(s, 3);
return r;
case 0xf0: if (s.len < 4) break;
if ((s.s[1]&0xc0) != 0x80) break;
if ((s.s[2]&0xc0) != 0x80) break;
if ((s.s[3]&0xc0) != 0x80) break;
r.rune = (i32)(s.s[0]&0x0f) << 18 |
(i32)(s.s[1]&0x3f) << 12 |
(i32)(s.s[2]&0x3f) << 6 |
(i32)(s.s[3]&0x3f) << 0;
if (r.rune < 0x10000) break;
if (r.rune > 0x10ffff) break;
r.tail = cuthead(s, 4);
return r;
}
r.rune = REPLACEMENT_CHARACTER;
r.tail = cuthead(s, 1);
return r;
}
// Encode code point returning the output length (1-4).
static i32 utf8encode_(u8 *s, c32 rune)
{
if (rune<0 || (rune>=0xd800 && rune<=0xdfff) || rune>0x10ffff) {
rune = REPLACEMENT_CHARACTER;
}
switch ((rune >= 0x80) + (rune >= 0x800) + (rune >= 0x10000)) {
case 0: s[0] = (u8)(0x00 | ((rune >> 0) )); return 1;
case 1: s[0] = (u8)(0xc0 | ((rune >> 6) ));
s[1] = (u8)(0x80 | ((rune >> 0) & 63)); return 2;
case 2: s[0] = (u8)(0xe0 | ((rune >> 12) ));
s[1] = (u8)(0x80 | ((rune >> 6) & 63));
s[2] = (u8)(0x80 | ((rune >> 0) & 63)); return 3;
case 3: s[0] = (u8)(0xf0 | ((rune >> 18) ));
s[1] = (u8)(0x80 | ((rune >> 12) & 63));
s[2] = (u8)(0x80 | ((rune >> 6) & 63));
s[3] = (u8)(0x80 | ((rune >> 0) & 63)); return 4;
}
assert(0);
}
typedef struct {
s16 tail;
c32 rune;
} utf16;
static utf16 utf16decode_(s16 s)
{
assert(s.len);
utf16 r = {0};
r.rune = s.s[0];
if (r.rune>=0xdc00 && r.rune<=0xdfff) {
goto reject; // unpaired low surrogate
} else if (r.rune>=0xd800 && r.rune<=0xdbff) {
if (s.len < 2) {
goto reject; // missing low surrogate
}
i32 hi = r.rune;
i32 lo = s.s[1];
if (lo<0xdc00 || lo>0xdfff) {
goto reject; // expected low surrogate
}
r.rune = 0x10000 + ((hi - 0xd800)<<10) + (lo - 0xdc00);
r.tail = s16cuthead_(s, 2);
return r;
}
r.tail = s16cuthead_(s, 1);
return r;
reject:
r.rune = REPLACEMENT_CHARACTER;
r.tail = s16cuthead_(s, 1);
return r;
}
// Encode code point returning the output length (1-2).
static i32 utf16encode_(c16 *dst, c32 rune)
{
if (rune<0 || (rune>=0xd800 && rune<=0xdfff) || rune>0x10ffff) {
rune = REPLACEMENT_CHARACTER;
}
if (rune >= 0x10000) {
rune -= 0x10000;
dst[0] = (c16)((rune >> 10) + 0xd800);
dst[1] = (c16)((rune&0x3ff) + 0xdc00);
return 2;
}
dst[0] = (c16)rune;
return 1;
}
static s16 towide_(arena *perm, s8 s)
{
iz len = 0;
utf8 state = {0};
state.tail = s;
while (state.tail.len) {
state = utf8decode_(state.tail);
c16 tmp[2];
len += utf16encode_(tmp, state.rune);
}
s16 w = {0};
w.s = new(perm, c16, len);
state.tail = s;
while (state.tail.len) {
state = utf8decode_(state.tail);
w.len += utf16encode_(w.s+w.len, state.rune);
}
return w;
}
static s8 fromwide_(arena *perm, s16 w)
{
iz len = 0;
utf16 state = {0};
state.tail = w;
while (state.tail.len) {
state = utf16decode_(state.tail);
u8 tmp[4];
len += utf8encode_(tmp, state.rune);
}
s8 s = {0};
s.s = new(perm, u8, len);
state.tail = w;
while (state.tail.len) {
state = utf16decode_(state.tail);
s.len += utf8encode_(s.s+s.len, state.rune);
}
return s;
}
static s8 fromenv_(arena *perm, c16 *name)
{
// Given no buffer, unset variables report as size 0, while empty
// variables report as size 1 for the null terminator.
i32 wlen = GetEnvironmentVariableW(name, 0, 0);
if (!wlen) {
s8 r = {0};
return r;
}
// Store temporarily at the beginning of the arena.
iz cap = (perm->end - perm->beg) / (iz)sizeof(c16);
if (wlen > cap) {
oom(perm->ctx);
}
s16 wvar = {0};
wvar.s = (c16 *)perm->beg;
wvar.len = wlen - 1;
GetEnvironmentVariableW(name, wvar.s, wlen);
byte *save = perm->beg;
perm->beg = (byte *)(wvar.s + wvar.len);
s8 var = fromwide_(perm, wvar);
perm->beg = save;
return var;
}
// Normalize path to slashes as separators.
static s8 normalize_(s8 path)
{
for (iz i = 0; i < path.len; i++) {
if (path.s[i] == '\\') {
path.s[i] = '/';
}
}
return path;
}
static i32 truncsize(iz len)
{
i32 max = 0x7fffffff;
return len>max ? max : (i32)len;
}
static s8 installdir_(arena *perm)
{
byte *save = perm->beg;
// GetModuleFileNameW does not communicate length. It only indicates
// success (buffer large enough) or failure (result truncated). To
// make matters worse, long paths have no fixed upper limit, though
// 64KiB is given as an approximate. To deal with this, offer the
// entire free region of the arena, far exceeding any path length.
//
// Computing sizes outside of the allocator isn't great, but the
// situation is constrained by this crummy API.
s16 exe = {0};
exe.s = (c16 *)perm->beg;
i32 cap = truncsize(perm->end - perm->beg) / (i32)sizeof(c16);
exe.len = GetModuleFileNameW(0, exe.s, cap);
perm->beg = (byte *)(exe.s + exe.len);
s8 path = normalize_(fromwide_(perm, exe));
perm->beg = save; // free the wide path
return dirname(dirname(path));
}
static s8 append2_(arena *perm, s8 pre, s8 suf)
{
s8 s = news8(perm, pre.len+suf.len);
s8copy(s8copy(s, pre), suf);
return s;
}
static s8 makepath_(arena *perm, s8 base, s8 lib, s8 share)
{
s8 delim = S(";");
iz len = base.len + lib.len + delim.len + base.len + share.len;
s8 s = news8(perm, len);
s8 r = s8copy(s, base);
r = s8copy(r, lib);
r = s8copy(r, delim);
r = s8copy(r, base);
s8copy(r, share);
return s;
}
static config *newconfig_(os *ctx)
{
arena perm = newarena_(1<<22);
perm.ctx = ctx;
config *conf = new(&perm, config, 1);
conf->perm = perm;
conf->haslisting = 1;
return conf;
}
__attribute((force_align_arg_pointer))
void mainCRTStartup(void)
{
os ctx[1] = {0};
i32 dummy;
ctx->handles[1].h = GetStdHandle(STD_OUTPUT_HANDLE);
ctx->handles[1].isconsole = GetConsoleMode(ctx->handles[1].h, &dummy);
ctx->handles[2].h = GetStdHandle(STD_ERROR_HANDLE);
ctx->handles[2].isconsole = GetConsoleMode(ctx->handles[2].h, &dummy);
config *conf = newconfig_(ctx);
conf->delim = ';';
conf->define_prefix = 1;
arena *perm = &conf->perm;
u8 **argv = new(perm, u8 *, CMDLINE_ARGV_MAX);
c16 *cmdline = GetCommandLineW();
conf->nargs = cmdline_to_argv8(cmdline, argv) - 1;
conf->args = argv + 1;
s8 base = installdir_(perm);
s8 lib = S(PKG_CONFIG_PREFIX "/lib/pkgconfig");
s8 share = S(PKG_CONFIG_PREFIX "/share/pkgconfig");
conf->pc_path = makepath_(perm, base, lib, share);
conf->pc_sysincpath = append2_(perm, base, S(PKG_CONFIG_PREFIX "/include"));
conf->pc_syslibpath = append2_(perm, base, S(PKG_CONFIG_PREFIX "/lib"));
conf->envpath = fromenv_(perm, L"PKG_CONFIG_PATH");
conf->fixedpath = fromenv_(perm, L"PKG_CONFIG_LIBDIR");
if (!conf->fixedpath.s) {
conf->fixedpath = conf->pc_path;
}
conf->top_builddir = fromenv_(perm, L"PKG_CONFIG_TOP_BUILD_DIR");
conf->sys_incpath = conf->pc_sysincpath;
conf->sys_libpath = conf->pc_syslibpath;
conf->print_sysinc = fromenv_(perm, L"PKG_CONFIG_ALLOW_SYSTEM_CFLAGS");
conf->print_syslib = fromenv_(perm, L"PKG_CONFIG_ALLOW_SYSTEM_LIBS");
// Reduce backslash occurrences in outputs
normalize_(conf->envpath);
normalize_(conf->fixedpath);
normalize_(conf->top_builddir);
uconfig(conf);
ExitProcess(ctx->handles[1].err || ctx->handles[2].err);
assert(0);
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
assert(ctx);
assert(path.len > 0);
assert(!path.s[path.len-1]);
filemap r = {0};
i32 handle = 0;
{
arena scratch = *perm;
s16 wpath = towide_(&scratch, path);
handle = CreateFileW(
wpath.s,
GENERIC_READ,
FILE_SHARE_ALL,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
);
if (handle == INVALID_HANDLE_VALUE) {
r.status = filemap_NOTFOUND;
return r;
}
}
r.data.s = (u8 *)perm->beg;
iz cap = perm->end - perm->beg;
while (r.data.len < cap) {
i32 len = truncsize(cap - r.data.len);
ReadFile(handle, r.data.s+r.data.len, len, &len, 0);
if (len < 1) {
break;
}
r.data.len += len;
}
CloseHandle(handle);
if (r.data.len == cap) {
// If it filled all available space, assume the file is too large.
r.status = filemap_READERR;
return r;
}
perm->beg += r.data.len;
r.status = filemap_OK;
return r;
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
assert(ctx);
assert(path.len > 0);
assert(!path.s[path.len-1]);
// NOTE: will allocate while holding this handle
iptr handle = -1;
finddata fd = {0};
{
arena scratch = *a;
u8buf buf = newmembuf(&scratch);
prints8(&buf, cuttail(path, 1));
prints8(&buf, S("\\*.pc\0"));
s8 glob = finalize(&buf);
s16 wide = towide_(&scratch, glob);
handle = FindFirstFileW(wide.s, &fd);
if (handle == -1) {
return 0;
}
}
s8list files = {0};
do {
s16 name = {0};
name.s = fd.name;
for (; name.s[name.len]; name.len++) {}
append(&files, fromwide_(a, name), a);
} while (FindNextFileW(handle, &fd));
FindClose(handle);
return files.head;
}
static void os_fail(os *ctx)
{
assert(ctx);
ExitProcess(1);
assert(0);
}
typedef struct {
c16 buf[1<<8];
iptr handle;
i32 len;
b32 err;
} u16buf;
static void flushconsole_(u16buf *b)
{
if (!b->err && b->len) {
i32 dummy;
b->err = !WriteConsoleW(b->handle, b->buf, b->len, &dummy, 0);
}
b->len = 0;
}
static void printc32_(u16buf *b, c32 rune)
{
if (b->len > countof(b->buf)-2) {
flushconsole_(b);
}
b->len += utf16encode_(b->buf+b->len, rune);
}
static void os_write(os *ctx, i32 fd, s8 s)
{
assert((i32)s.len == s.len); // NOTE: assume it's not a huge buffer
assert(fd==1 || fd==2);
b32 *err = &ctx->handles[fd].err;
if (*err) {
return;
}
iptr handle = ctx->handles[fd].h;
if (ctx->handles[fd].isconsole) {
// NOTE: There is a small chance that a multi-byte code point
// spans flushes from the application. With no decoder state
// tracked between os_write calls, this will mistranslate for
// console outputs. The application could avoid such flushes,
// which would require a distinct "close" flush before exits.
//
// Alternatively, the platform layer could detect truncated
// encodings and buffer up to 3 bytes between calls. This buffer
// would need to be flushed on exit by the platform.
//
// The primary use case for u-config is non-console outputs into
// a build system, which will not experience this issue. Console
// output is mainly for human-friendly debugging, so the risk is
// acceptable.
u16buf b = {0};
b.handle = handle;
utf8 state = {0};
state.tail = s;
while (state.tail.len) {
state = utf8decode_(state.tail);
printc32_(&b, state.rune);
}
flushconsole_(&b);
*err = b.err;
} else {
i32 dummy;
*err = !WriteFile(handle, s.s, (i32)s.len, &dummy, 0);
}
}
u-config-0.34.0/src/ 0000775 0000000 0000000 00000000000 15002525747 0014103 5 ustar 00root root 0000000 0000000 u-config-0.34.0/src/cmdline.c 0000664 0000000 0000000 00000011400 15002525747 0015656 0 ustar 00root root 0000000 0000000 #define CMDLINE_CMD_MAX 32767 // max command line length on Windows
#define CMDLINE_ARGV_MAX (16384+(98298+(i32)sizeof(u8 *))/(i32)sizeof(u8 *))
// Convert an ill-formed-UTF-16 command line to a WTF-8 argv following
// field splitting semantics identical to CommandLineToArgvW, including
// undocumented behavior. Populates argv with pointers into itself and
// returns argc, which is always positive.
//
// Expects that cmd has no more than 32,767 (CMDLINE_CMD_MAX) elements
// including the null terminator, and argv has at least CMDLINE_ARGV_MAX
// elements. This covers the worst possible cases for a Windows command
// string, so no further allocation is ever necessary.
//
// Unlike CommandLineToArgvW, when the command line string is zero
// length this function does not invent an artificial argv[0] based on
// the calling module file name. To implement this behavior yourself,
// test if cmd[0] is zero and then act accordingly.
//
// If the input is UTF-16, then the output is UTF-8.
static i32 cmdline_to_argv8(c16 *cmd, u8 **argv)
{
i32 argc = 1; // worst case: argv[0] is an empty string
i32 state = 6; // special argv[0] state
i32 slash = 0;
// Use second half as byte buffer
u8 *buf = (u8 *)(argv + 16384);
argv[0] = buf;
while (*cmd) {
i32 c = *cmd++;
if (c>>10 == 0x36 && *cmd>>10 == 0x37) { // surrogates?
c = 0x10000 + ((c - 0xd800)<<10) + (*cmd++ - 0xdc00);
}
switch (state) {
case 0: switch (c) { // outside token
case 0x09:
case 0x20: continue;
case 0x22: argv[argc++] = buf;
state = 2;
continue;
case 0x5c: argv[argc++] = buf;
slash = 1;
state = 3;
break;
default : argv[argc++] = buf;
state = 1;
} break;
case 1: switch (c) { // inside unquoted token
case 0x09:
case 0x20: *buf++ = 0;
state = 0;
continue;
case 0x22: state = 2;
continue;
case 0x5c: slash = 1;
state = 3;
break;
} break;
case 2: switch (c) { // inside quoted token
case 0x22: state = 5;
continue;
case 0x5c: slash = 1;
state = 4;
break;
} break;
case 3:
case 4: switch (c) { // backslash sequence
case 0x22: buf -= (1 + slash) >> 1;
if (slash & 1) {
state -= 2;
break;
} // fallthrough
default : cmd -= 1 + (c >= 0x10000);
state -= 2;
continue;
case 0x5c: slash++;
} break;
case 5: switch (c) { // quoted token exit
default : cmd -= 1 + (c >= 0x10000);
state = 1;
continue;
case 0x22: state = 1;
} break;
case 6: switch (c) { // begin argv[0]
case 0x09:
case 0x20: *buf++ = 0;
state = 0;
continue;
case 0x22: state = 8;
continue;
default : state = 7;
} break;
case 7: switch (c) { // unquoted argv[0]
case 0x09:
case 0x20: *buf++ = 0;
state = 0;
continue;
} break;
case 8: switch (c) { // quoted argv[0]
case 0x22: *buf++ = 0;
state = 0;
continue;
} break;
}
// WTF-8/UTF-8 encoding
switch ((c >= 0x80) + (c >= 0x800) + (c >= 0x10000)) {
case 0: *buf++ = (u8)(0x00 | ((c >> 0) )); break;
case 1: *buf++ = (u8)(0xc0 | ((c >> 6) ));
*buf++ = (u8)(0x80 | ((c >> 0) & 63)); break;
case 2: *buf++ = (u8)(0xe0 | ((c >> 12) ));
*buf++ = (u8)(0x80 | ((c >> 6) & 63));
*buf++ = (u8)(0x80 | ((c >> 0) & 63)); break;
case 3: *buf++ = (u8)(0xf0 | ((c >> 18) ));
*buf++ = (u8)(0x80 | ((c >> 12) & 63));
*buf++ = (u8)(0x80 | ((c >> 6) & 63));
*buf++ = (u8)(0x80 | ((c >> 0) & 63)); break;
}
}
*buf = 0;
argv[argc] = 0;
return argc;
}
u-config-0.34.0/src/linux_noarch.c 0000664 0000000 0000000 00000012547 15002525747 0016751 0 ustar 00root root 0000000 0000000 // libc-free platform layer for Linux (arch-agnostic parts)
// This is free and unencumbered software released into the public domain.
#ifndef PKG_CONFIG_LIBDIR
# define PKG_CONFIG_LIBDIR \
"/usr/local/lib/pkgconfig:" \
"/usr/local/share/pkgconfig:" \
"/usr/lib/pkgconfig:" \
"/usr/share/pkgconfig"
#endif
#ifndef PKG_CONFIG_SYSTEM_INCLUDE_PATH
# define PKG_CONFIG_SYSTEM_INCLUDE_PATH "/usr/include"
#endif
#ifndef PKG_CONFIG_SYSTEM_LIBRARY_PATH
# define PKG_CONFIG_SYSTEM_LIBRARY_PATH "/lib:/usr/lib"
#endif
// Arch-specific definitions, defined by the includer. Also requires
// macro definitions for SYS_read, SYS_write, SYS_open, SYS_close,
// SYS_exit. Arch-specific _start calls arch-agnostic entrypoint() with
// the process entry stack pointer.
static long syscall1(long, long);
static long syscall2(long, long, long);
static long syscall3(long, long, long, long);
static void os_fail(os *ctx)
{
(void)ctx;
syscall1(SYS_exit, 1);
__builtin_unreachable();
}
static void os_write(os *ctx, i32 fd, s8 s)
{
(void)ctx;
assert(fd==1 || fd==2);
while (s.len) {
long r = syscall3(SYS_write, fd, (long)s.s, s.len);
if (r < 0) {
os_fail(0);
}
s = cuthead(s, (iz)r);
}
}
static filemap os_mapfile(os *ctx, arena *perm, s8 path)
{
(void)ctx;
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
filemap r = {0};
long fd = syscall2(SYS_open, (long)path.s, 0);
if (fd < 0) {
r.status = filemap_NOTFOUND;
return r;
}
r.data.s = (u8 *)perm->beg;
iz cap = perm->end - perm->beg;
while (r.data.len < cap) {
u8 *dst = r.data.s + r.data.len;
long len = syscall3(SYS_read, fd, (long)dst, cap-r.data.len);
if (len < 1) {
break;
}
r.data.len += len;
}
syscall1(SYS_close, fd);
if (r.data.len == cap) {
// If it filled all available space, assume the file is too large
r.status = filemap_READERR;
return r;
}
perm->beg += r.data.len;
r.status = filemap_OK;
return r;
}
static b32 endswith_(s8 s, s8 suffix)
{
return s.len>=suffix.len && s8equals(taketail(s, suffix.len), suffix);
}
static s8node *os_listing(os *ctx, arena *a, s8 path)
{
(void)ctx;
assert(path.s);
assert(path.len);
assert(!path.s[path.len-1]);
// NOTE: will allocate while holding this file descriptor
enum { O_DIRECTORY = 0x10000 };
int fd = (int)syscall2(SYS_open, (long)path.s, O_DIRECTORY);
if (fd < 0) {
return 0;
}
iz cap = 1<<14;
byte *buf = new(a, byte, cap);
s8list files = {0};
for (;;) {
iz len = syscall3(SYS_getdents64, fd, (long)buf, cap);
if (len < 1) {
break;
}
for (iz off = 0; off < len;) {
struct {
long long ino;
long long off;
short len;
char type;
u8 name[];
} *dents = (void *)(buf + off);
off += dents->len;
s8 name = s8fromcstr(dents->name);
if (endswith_(name, S(".pc"))) {
s8 copy = news8(a, name.len);
s8copy(copy, name);
append(&files, copy, a);
}
}
}
syscall1(SYS_close, fd);
return files.head;
}
static arena getarena_(void)
{
static byte heap[1<<22];
byte *mem = heap;
asm ("" : "+r"(mem)); // launder
return (arena){mem, mem+countof(heap), 0};
}
static config *newconfig_(void)
{
arena perm = getarena_();
config *conf = new(&perm, config, 1);
conf->perm = perm;
conf->haslisting = 1;
return conf;
}
static i32 os_main(i32 argc, u8 **argv, u8 **envp)
{
config *conf = newconfig_();
conf->delim = ':';
if (argc) {
argc--;
argv++;
}
conf->nargs = argc;
conf->args = argv;
conf->pc_path = S(PKG_CONFIG_LIBDIR);
conf->pc_sysincpath = S(PKG_CONFIG_SYSTEM_INCLUDE_PATH);
conf->pc_syslibpath = S(PKG_CONFIG_SYSTEM_LIBRARY_PATH);
conf->fixedpath = S(PKG_CONFIG_LIBDIR);
conf->sys_incpath = S(PKG_CONFIG_SYSTEM_INCLUDE_PATH);
conf->sys_libpath = S(PKG_CONFIG_SYSTEM_LIBRARY_PATH);
for (u8 **v = envp; *v; v++) {
cut c = s8cut(s8fromcstr(*v), '=');
s8 name = c.head;
s8 value = c.tail;
if (s8equals(name, S("PKG_CONFIG_PATH"))) {
conf->envpath = value;
} else if (s8equals(name, S("PKG_CONFIG_LIBDIR"))) {
conf->fixedpath = value;
} else if (s8equals(name, S("PKG_CONFIG_TOP_BUILD_DIR"))) {
conf->top_builddir = value;
} else if (s8equals(name, S("PKG_CONFIG_SYSTEM_INCLUDE_PATH"))) {
conf->sys_incpath = value;
} else if (s8equals(name, S("PKG_CONFIG_SYSTEM_LIBRARY_PATH"))) {
conf->sys_libpath = value;
} else if (s8equals(name, S("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS"))) {
conf->print_sysinc = value;
} else if (s8equals(name, S("PKG_CONFIG_ALLOW_SYSTEM_LIBS"))) {
conf->print_syslib = value;
}
}
uconfig(conf);
return 0;
}
void entrypoint(long *stack)
{
i32 argc = (i32)stack[0];
u8 **argv = (u8 **)stack + 1;
u8 **envp = argv + argc + 1;
i32 status = os_main(argc, argv, envp);
syscall1(SYS_exit, status);
__builtin_unreachable();
}
u-config-0.34.0/src/miniwin32.h 0000664 0000000 0000000 00000002672 15002525747 0016102 0 ustar 00root root 0000000 0000000 // Win32 types, constants, and declarations (replaces windows.h)
// This is free and unencumbered software released into the public domain.
typedef ptrdiff_t iptr;
typedef size_t uptr;
typedef unsigned short char16_t;
typedef char16_t c16;
enum {
FILE_ATTRIBUTE_NORMAL = 0x80,
FILE_SHARE_ALL = 7,
GENERIC_READ = (i32)0x80000000,
INVALID_HANDLE_VALUE = -1,
MEM_COMMIT = 0x1000,
MEM_RESERVE = 0x2000,
OPEN_EXISTING = 3,
PAGE_READWRITE = 4,
STD_OUTPUT_HANDLE = -11,
STD_ERROR_HANDLE = -12,
};
typedef struct {
i32 attr;
u32 create[2], access[2], write[2];
u32 size[2];
u32 reserved1[2];
c16 name[260];
c16 altname[14];
u32 reserved2[2];
} finddata;
#define W32(r) __declspec(dllimport) r __stdcall
W32(b32) CloseHandle(iptr);
W32(i32) CreateFileW(c16 *, i32, i32, uptr, i32, i32, i32);
W32(void) ExitProcess(i32);
W32(b32) FindClose(iptr);
W32(iptr) FindFirstFileW(c16 *, finddata *);
W32(b32) FindNextFileW(iptr, finddata *);
W32(c16 *) GetCommandLineW(void);
W32(b32) GetConsoleMode(iptr, i32 *);
W32(i32) GetEnvironmentVariableW(c16 *, c16 *, i32);
W32(i32) GetModuleFileNameW(iptr, c16 *, i32);
W32(iptr) GetStdHandle(i32);
W32(b32) ReadFile(iptr, u8 *, i32, i32 *, uptr);
W32(byte *) VirtualAlloc(uptr, iz, i32, i32);
W32(b32) WriteConsoleW(iptr, c16 *, i32, i32 *, uptr);
W32(b32) WriteFile(iptr, u8 *, i32, i32 *, uptr);
u-config-0.34.0/src/u-config.c 0000664 0000000 0000000 00000154460 15002525747 0015770 0 ustar 00root root 0000000 0000000 // u-config: a small, simple, portable pkg-config clone
// https://github.com/skeeto/u-config
// This is free and unencumbered software released into the public domain.
#include
#define VERSION "0.34.0"
typedef unsigned char u8;
typedef signed int b32;
typedef signed int i32;
typedef unsigned int u32;
typedef ptrdiff_t iz;
typedef char byte;
#define assert(c) while (!(c)) __builtin_unreachable()
#define countof(a) (iz)(sizeof(a) / sizeof(*(a)))
#define new(a, t, n) (t *)alloc(a, sizeof(t), n)
#define s8(s) {(u8 *)s, countof(s)-1}
#define S(s) (s8)s8(s)
typedef struct os os;
typedef struct {
u8 *s;
iz len;
} s8;
typedef struct s8node s8node;
struct s8node {
s8node *next;
s8 str;
};
typedef struct {
byte *beg;
byte *end;
os *ctx;
} arena;
typedef struct {
arena perm;
u8 **args;
i32 nargs;
s8 pc_path; // default compile time fixedpath
s8 pc_sysincpath; // default compile time system include path
s8 pc_syslibpath; // default compile time system library path
s8 envpath; // $PKG_CONFIG_PATH or empty
s8 fixedpath; // $PKG_CONFIG_LIBDIR or default
s8 top_builddir; // $PKG_CONFIG_TOP_BUILD_DIR or default
s8 sys_incpath; // $PKG_CONFIG_SYSTEM_INCLUDE_PATH or default
s8 sys_libpath; // $PKG_CONFIG_SYSTEM_LIBRARY_PATH or default
s8 print_sysinc; // $PKG_CONFIG_ALLOW_SYSTEM_CFLAGS or empty
s8 print_syslib; // $PKG_CONFIG_ALLOW_SYSTEM_LIBS or empty
b32 define_prefix;
b32 haslisting;
u8 delim;
} config;
// Platform API
// Application entry point. Returning from this function indicates the
// application itself completed successfully. However, an os_write error
// may result in a non-zero exit.
static void uconfig(config *);
enum { filemap_OK, filemap_NOTFOUND, filemap_READERR };
typedef struct {
s8 data;
i32 status;
} filemap;
// Load a file into memory, maybe using the arena. The path must include
// a null terminator since it may be passed directly to the OS interface.
static filemap os_mapfile(os *, arena *, s8 path);
// List all .pc files under a particular path. The path must include a
// null terminator since it may be passed directly to the OS interface.
static s8node *os_listing(os *, arena *, s8 path);
// Write buffer to stdout (1) or stderr (2). The platform must detect
// write errors and arrange for an eventual non-zero exit status.
static void os_write(os *, i32 fd, s8);
// Immediately exit the program with a non-zero status.
static void os_fail(os *) __attribute((noreturn));
// Application
static void oom(os *ctx)
{
os_write(ctx, 2, S("pkg-config: out of memory\n"));
os_fail(ctx);
}
static b32 digit(u8 c)
{
return c>='0' && c<='9';
}
static b32 whitespace(u8 c)
{
switch (c) {
case '\t': case '\n': case '\r': case ' ':
return 1;
}
return 0;
}
static byte *fillbytes(byte *dst, byte c, iz len)
{
byte *r = dst;
for (; len; len--) {
*dst++ = c;
}
return r;
}
static void u8copy(u8 *dst, u8 *src, iz n)
{
assert(n >= 0);
for (; n; n--) {
*dst++ = *src++;
}
}
static i32 u8compare(u8 *a, u8 *b, iz n)
{
for (; n; n--) {
i32 d = *a++ - *b++;
if (d) return d;
}
return 0;
}
static b32 pathsep(u8 c)
{
return c=='/' || c=='\\';
}
static byte *alloc(arena *a, iz size, iz count)
{
assert(size > 0);
assert(count >= 0);
iz alignment = -((u32)size * (u32)count) & 7;
iz available = a->end - a->beg - alignment;
if (count > available/size) {
oom(a->ctx);
}
iz total = size * count;
return fillbytes(a->end -= total + alignment, 0, total);
}
static s8 news8(arena *perm, iz len)
{
s8 r = {0};
r.s = new(perm, u8, len);
r.len = len;
return r;
}
static s8 s8span(u8 *beg, u8 *end)
{
assert(beg);
assert(end);
assert(end >= beg);
s8 s = {0};
s.s = beg;
s.len = end - beg;
return s;
}
static s8 s8fromcstr(u8 *z)
{
s8 s = {0};
if (z) {
s.s = (u8 *)z;
for (; s.s[s.len]; s.len++) {}
}
return s;
}
// Copy src into dst returning the remaining portion of dst.
static s8 s8copy(s8 dst, s8 src)
{
assert(dst.len >= src.len);
u8copy(dst.s, src.s, src.len);
dst.s += src.len;
dst.len -= src.len;
return dst;
}
static b32 s8equals(s8 a, s8 b)
{
return a.len==b.len && !u8compare(a.s, b.s, a.len);
}
static s8 cuthead(s8 s, iz off)
{
assert(off >= 0);
assert(off <= s.len);
s.s += off;
s.len -= off;
return s;
}
static s8 takehead(s8 s, iz len)
{
assert(len >= 0);
assert(len <= s.len);
s.len = len;
return s;
}
static s8 cuttail(s8 s, iz len)
{
assert(len >= 0);
assert(len <= s.len);
s.len -= len;
return s;
}
static s8 taketail(s8 s, iz len)
{
return cuthead(s, s.len-len);
}
static b32 startswith(s8 s, s8 prefix)
{
return s.len>=prefix.len && s8equals(takehead(s, prefix.len), prefix);
}
static u32 s8hash(s8 s)
{
u32 h = 0x811c9dc5;
for (iz i = 0; i < s.len; i++) {
h ^= s.s[i];
h *= 0x01000193;
}
return h;
}
typedef struct {
s8 head;
s8 tail;
} s8pair;
static s8pair digits(s8 s)
{
iz len = 0;
for (; lencap = cap;
b->buf = new(perm, u8, cap);
b->fd = fd;
b->ctx = perm->ctx;
return b;
}
static u8buf *newnullout(arena *perm)
{
u8buf *b = new(perm, u8buf, 1);
b->fd = -1;
b->ctx = perm->ctx;
return b;
}
// Output to a dynamically-grown arena buffer. The arena cannot be used
// again until this buffer is finalized.
static u8buf newmembuf(arena *perm)
{
u8buf b = {0};
b.buf = (u8 *)perm->beg;
b.cap = perm->end - perm->beg;
b.perm = perm;
b.ctx = perm->ctx;
return b;
}
static s8 gets8(u8buf *b)
{
s8 s = {0};
s.s = b->buf;
s.len = b->len;
return s;
}
// Close the stream and release the arena, returning the result buffer.
static s8 finalize(u8buf *b)
{
assert(!b->fd);
b->perm->beg += b->len;
return gets8(b);
}
static void flush(u8buf *b)
{
switch (b->fd) {
case -1: break; // /dev/null
case 0: oom(b->ctx);
break;
default: if (b->len) {
os_write(b->ctx, b->fd, gets8(b));
}
}
b->len = 0;
}
static void prints8(u8buf *b, s8 s)
{
if (b->fd == -1) {
return; // /dev/null
}
for (iz off = 0; off < s.len;) {
iz avail = b->cap - b->len;
iz count = availbuf+b->len, s.s+off, count);
b->len += count;
off += count;
if (b->len == b->cap) {
flush(b);
}
}
}
static void printu8(u8buf *b, u8 c)
{
prints8(b, s8span(&c, &c+1));
}
typedef struct env env;
struct env {
env *child[4];
s8 name;
s8 value;
};
// Return a pointer to the binding so that the caller can bind it. The
// arena is optional. If given, the binding will be created and set to a
// null string. A null pointer is a valid empty environment.
static s8 *insert(env **e, s8 name, arena *perm)
{
for (u32 h = s8hash(name); *e; h <<= 2) {
if (s8equals((*e)->name, name)) {
return &(*e)->value;
}
e = &(*e)->child[h>>30];
}
if (!perm) {
return 0;
}
*e = new(perm, env, 1);
(*e)->name = name;
return &(*e)->value;
}
// Try to find the binding in the global environment, then failing that,
// the second environment. Returns a null string if no entry was found.
// A null pointer is valid for lookups.
static s8 lookup(env *global, env *env, s8 name)
{
s8 *s = 0;
s8 null = {0};
s = s ? s : insert(&global, name, 0);
s = s ? s : insert(&env, name, 0);
s = s ? s : &null;
return *s;
}
static s8 dirname(s8 path)
{
iz len = path.len;
while (len>0 && !pathsep(path.s[--len])) {}
return takehead(path, len);
}
static s8 basename(s8 path)
{
iz len = path.len;
for (; len>0 && !pathsep(path.s[len-1]); len--) {}
return taketail(path, path.len-len);
}
static s8 buildpath(s8 dir, s8 pc, arena *perm)
{
s8 sep = S("/");
s8 suffix = S(".pc\0");
iz pathlen = dir.len + sep.len + pc.len + suffix.len;
s8 path = news8(perm, pathlen);
s8 p = path;
p = s8copy(p, dir);
p = s8copy(p, sep);
p = s8copy(p, pc);
s8copy(p, suffix);
return path;
}
typedef enum {
versop_ERR=0,
versop_LT,
versop_LTE,
versop_EQ,
versop_GTE,
versop_GT
} versop;
static versop parseop(s8 s)
{
if (s8equals(S("<"), s)) {
return versop_LT;
} else if (s8equals(S("<="), s)) {
return versop_LTE;
} else if (s8equals(S("="), s)) {
return versop_EQ;
} else if (s8equals(S(">="), s)) {
return versop_GTE;
} else if (s8equals(S(">"), s)) {
return versop_GT;
}
return versop_ERR;
}
static s8 opname(versop op)
{
switch (op) {
case versop_ERR: break;
case versop_LT: return S("<");
case versop_LTE: return S("<=");
case versop_EQ: return S("=");
case versop_GTE: return S(">=");
case versop_GT: return S(">");
}
assert(0);
}
static b32 validcompare(versop op, i32 result)
{
switch (op) {
case versop_ERR: break;
case versop_LT: return result < 0;
case versop_LTE: return result <= 0;
case versop_EQ: return result == 0;
case versop_GTE: return result >= 0;
case versop_GT: return result > 0;
}
assert(0);
}
typedef struct pkgspec pkgspec;
struct pkgspec {
pkgspec *next;
s8 name;
versop op;
s8 version;
};
enum { pkg_DIRECT=1<<0, pkg_PUBLIC=1<<1 };
typedef struct pkg pkg;
struct pkg {
pkg *child[4];
pkg *list; // total load order list
s8 path;
s8 realname;
s8 contents;
env *env;
pkgspec *specs_requires;
pkgspec *specs_requiresprivate;
i32 flags;
#define PKG_NFIELDS 11
s8 name;
s8 description;
s8 url;
s8 version;
s8 requires;
s8 requiresprivate;
s8 conflicts;
s8 libs;
s8 libsprivate;
s8 cflags;
s8 cflagsprivate;
};
static s8 *fieldbyid(pkg *p, i32 id)
{
assert(id >= 0);
assert(id < PKG_NFIELDS);
return (s8 *)((byte *)&p->name + id*(i32)sizeof(s8));
}
static s8 *fieldbyname(pkg *p, s8 name)
{
static const s8 fields[] = {
s8("Name"),
s8("Description"),
s8("URL"),
s8("Version"),
s8("Requires"),
s8("Requires.private"),
s8("Conflicts"),
s8("Libs"),
s8("Libs.private"),
s8("Cflags"),
s8("Cflags.private")
};
for (i32 i = 0; i < countof(fields); i++) {
if (s8equals(fields[i], name)) {
return fieldbyid(p, i);
}
}
return 0;
}
typedef struct {
pkg *pkgs;
pkg *head;
iz count;
} pkgs;
// Locate a previously-loaded package, or allocate zero-initialized
// space in the set for a new package.
static pkg *locate(pkgs *t, s8 realname, arena *perm)
{
pkg **p = &t->pkgs;
for (u32 h = s8hash(realname); *p; h <<= 2) {
if (s8equals((*p)->realname, realname)) {
return *p;
}
p = &(*p)->child[h>>30];
}
*p = new(perm, pkg, 1);
(*p)->realname = realname;
t->count++;
return *p;
}
static void prepend(pkgs *t, pkg *p)
{
assert(!p->list);
p->list = t->head;
t->head = p;
}
static b32 allpresent(pkgs t)
{
iz count = 0;
for (pkg *p = t.head; p; p = p->list) {
count++;
}
return count == t.count;
}
enum { parse_OK, parse_DUPFIELD, parse_DUPVARABLE };
typedef struct {
pkg pkg;
s8 dupname;
i32 err;
} parseresult;
// Return the number of escape bytes at the beginning of the input.
static iz escaped(s8 s)
{
if (startswith(s, S("\\\n"))) {
return 2;
}
if (startswith(s, S("\\\r\n"))) {
return 3;
}
return 0;
}
// Return a copy of the input with the escapes squashed out.
static s8 stripescapes(arena *perm, s8 s)
{
iz len = 0;
s8 c = news8(perm, s.len);
for (iz i = 0; i < s.len; i++) {
u8 b = s.s[i];
if (b == '\\') {
iz r = escaped(cuthead(s, i));
if (r) {
i += r - 1;
} else if (inext = next;
r->name = name;
return r;
}
static void checknotop(u8buf *err, s8 tok, pkg *p)
{
if (parseop(tok)) {
prints8(err, S("pkg-config: "));
prints8(err, S("unexpected operator '"));
prints8(err, tok);
prints8(err, S("'"));
if (p) {
prints8(err, S(" in package '"));
prints8(err, p->realname);
prints8(err, S("'"));
}
prints8(err, S("\n"));
flush(err);
os_fail(err->ctx);
}
}
static void opfail(u8buf *err, versop op, pkg *p)
{
prints8(err, S("pkg-config: "));
prints8(err, S("expected version following operator "));
prints8(err, opname(op));
if (p) {
prints8(err, S(" in package '"));
prints8(err, p->realname);
prints8(err, S("'"));
}
prints8(err, S("\n"));
flush(err);
os_fail(err->ctx);
}
static pkgspec *parsespecs(s8 *args, iz nargs, pkg *p, u8buf *err, arena *a)
{
pkgspec *head = 0;
pkgspec *pkg = 0;
for (iz i = 0; i < nargs; i++) {
s8pair sp = {0};
sp.tail = args[i];
for (;;) {
sp = nexttoken(sp.tail);
s8 tok = sp.head;
if (!tok.len) {
break;
}
if (!pkg) {
checknotop(err, tok, p);
head = pkg = newpkgspec(a, tok, head);
} else if (!pkg->op) {
pkg->op = parseop(tok);
if (!pkg->op) {
head = pkg = newpkgspec(a, tok, head);
}
} else {
pkg->version = tok;
pkg = 0;
}
}
}
if (pkg && pkg->op && !pkg->version.s) {
opfail(err, pkg->op, p);
}
return head;
}
static parseresult parsepackage(s8 src, arena *perm)
{
u8 *p = src.s;
u8 *e = src.s + src.len;
parseresult result = {0};
result.err = parse_OK;
result.pkg.contents = src;
while (p < e) {
for (; ps) {
parseresult dup = {0};
dup.dupname = name;
dup.err = parse_DUPVARABLE;
return dup;
}
break;
case ':':
field = fieldbyname(&result.pkg, name);
if (field && field->s) {
parseresult dup = {0};
dup.dupname = name;
dup.err = parse_DUPFIELD;
return dup;
}
break;
}
// Skip leading space; newlines may be escaped with a backslash
while (p < e) {
if (*p == '\\') {
iz r = escaped(s8span(p, e));
if (r) {
p += r;
} else {
break;
}
} else if (*p=='\n' || !whitespace(*p)) {
break;
} else {
p++;
}
}
b32 cleanup = 0;
end = beg = p;
for (; pctx);
}
typedef struct {
iz nargs;
s8 *args;
iz index;
b32 dashdash;
} options;
static options newoptions(s8 *args, iz nargs)
{
options r = {0};
r.nargs = nargs;
r.args = args;
return r;
}
typedef struct {
s8 arg;
s8 value;
b32 isoption;
b32 ok;
} optresult;
static optresult nextoption(options *p)
{
optresult r = {0};
for (;;) {
if (p->index == p->nargs) {
return r;
}
s8 arg = p->args[p->index++];
if (p->dashdash || arg.len<2 || arg.s[0]!='-') {
r.arg = arg;
r.ok = 1;
return r;
}
if (!p->dashdash && s8equals(arg, S("--"))) {
p->dashdash = 1;
continue;
}
r.isoption = 1;
r.ok = 1;
arg = cuthead(arg, 1);
cut c = s8cut(arg, '=');
if (c.ok) {
r.arg = c.head;
r.value = c.tail;
} else {
r.arg = arg;
}
return r;
}
}
static s8 getargopt(u8buf *err, options *p, s8 option)
{
if (p->index == p->nargs) {
missing(err, option);
}
return p->args[p->index++];
}
static void usage(u8buf *b)
{
static const u8 usage[] =
"u-config " VERSION " https://github.com/skeeto/u-config\n"
"free and unencumbered software released into the public domain\n"
"usage: pkg-config [OPTIONS...] [PACKAGES...]\n"
" --cflags, --cflags-only-I, --cflags-only-other\n"
" --define-prefix, --dont-define-prefix\n"
" --define-variable=NAME=VALUE, --variable=NAME\n"
" --exists, --validate, --{atleast,exact,max}-version=VERSION\n"
" --errors-to-stdout\n"
" --keep-system-cflags, --keep-system-libs\n"
" --libs, --libs-only-L, --libs-only-l, --libs-only-other\n"
" --list-all\n"
" --list-package-names\n"
" --maximum-traverse-depth=N\n"
" --modversion\n"
" --msvc-syntax\n"
" --newlines\n"
" --silence-errors\n"
" --static\n"
" --with-path=PATH\n"
" -h, --help, --version\n"
"environment:\n"
" PKG_CONFIG_PATH\n"
" PKG_CONFIG_LIBDIR\n"
" PKG_CONFIG_TOP_BUILD_DIR\n"
" PKG_CONFIG_SYSTEM_INCLUDE_PATH\n"
" PKG_CONFIG_SYSTEM_LIBRARY_PATH\n"
" PKG_CONFIG_ALLOW_SYSTEM_CFLAGS\n"
" PKG_CONFIG_ALLOW_SYSTEM_LIBS\n";
prints8(b, S(usage));
}
typedef struct {
s8node *head;
s8node **tail;
} s8list;
static void append(s8list *list, s8 str, arena *perm)
{
if (!list->tail) {
list->tail = &list->head;
}
s8node *node = new(perm, s8node, 1);
node->str = str;
*list->tail = node;
list->tail = &node->next;
}
typedef struct {
s8list list;
u8 delim;
} search;
static search newsearch(u8 delim)
{
search r = {0};
r.delim = delim;
return r;
}
static void appendpath(search *dirs, s8 path, arena *perm)
{
while (path.len) {
cut c = s8cut(path, dirs->delim);
s8 dir = c.head;
if (dir.len) {
append(&dirs->list, dir, perm);
}
path = c.tail;
}
}
static void prependpath(search *dirs, s8 path, arena *perm)
{
if (!dirs->list.head) {
// Empty, so appending is the same a prepending
appendpath(dirs, path, perm);
} else {
// Append to an empty Search, then transplant in front
search temp = newsearch(dirs->delim);
appendpath(&temp, path, perm);
*temp.list.tail = dirs->list.head;
dirs->list.head = temp.list.head;
}
}
static b32 realnameispath(s8 realname)
{
return realname.len>3 && s8equals(taketail(realname, 3), S(".pc"));
}
static s8 pathtorealname(s8 path)
{
if (!realnameispath(path)) {
return path;
}
iz baselen = 0;
for (iz i = 0; i < path.len; i++) {
if (pathsep(path.s[i])) {
baselen = i + 1;
}
}
s8 name = cuthead(path, baselen);
return cuttail(name, 3);
}
static s8 readpackage(u8buf *err, s8 path, s8 realname, arena *perm)
{
if (s8equals(realname, S("pkg-config"))) {
return S(
"Name: u-config\n"
"Version: " VERSION "\n"
"Description:\n"
);
}
s8 null = {0};
filemap m = os_mapfile(perm->ctx, perm, path);
switch (m.status) {
case filemap_NOTFOUND:
return null;
case filemap_READERR:
prints8(err, S("pkg-config: "));
prints8(err, S("could not read package '"));
prints8(err, realname);
prints8(err, S("' from '"));
prints8(err, path);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
case filemap_OK:
return m.data;
}
assert(0);
}
static void expand(u8buf *out, u8buf *err, env *global, pkg *p, s8 str)
{
i32 top = 0;
s8 stack[128];
stack[top] = str;
while (top >= 0) {
s8 s = stack[top--];
for (iz i = 0; i < s.len-1; i++) {
if (s.s[i]=='$' && s.s[i+1]=='{') {
if (top >= countof(stack)-2) {
prints8(err, S("pkg-config: "));
prints8(err, S("exceeded max recursion depth in '"));
prints8(err, p->path);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
prints8(out, takehead(s, i));
iz beg = i + 2;
iz end = beg;
for (; endenv, name);
if (!value.s) {
prints8(err, S("pkg-config: "));
prints8(err, S("undefined variable '"));
prints8(err, name);
prints8(err, S("' in '"));
prints8(err, p->path);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
stack[++top] = value;
s.len = 0;
break;
} else if (s.s[i]=='$' && s.s[i+1]=='$') {
s8 head = takehead(s, i+1);
prints8(out, head);
stack[++top] = cuthead(s, i+2);
s.len = 0;
break;
}
}
prints8(out, s);
}
}
// Merge and expand data from "update" into "base".
static void expandmerge(u8buf *err, env *g, pkg *base, pkg *update, arena *perm)
{
base->path = update->path;
base->contents = update->contents;
base->env = update->env;
base->flags = update->flags;
for (i32 i = 0; i < PKG_NFIELDS; i++) {
u8buf mem = newmembuf(perm);
s8 src = *fieldbyid(update, i);
expand(&mem, err, g, update, src);
*fieldbyid(base, i) = finalize(&mem);
}
base->specs_requires = parsespecs(
&base->requires, 1, base, err, perm
);
base->specs_requiresprivate = parsespecs(
&base->requiresprivate, 1, base, err, perm
);
}
static pkg findpackage(search *dirs, u8buf *err, s8 realname, arena *perm)
{
s8 path = {0};
s8 contents = {0};
if (realnameispath(realname)) {
path = news8(perm, realname.len+1);
s8copy(path, realname).s[0] = 0;
contents = readpackage(err, path, realname, perm);
path = cuttail(path, 1); // remove null terminator
if (contents.s) {
realname = pathtorealname(path);
}
}
for (s8node *n = dirs->list.head; n && !contents.s; n = n->next) {
path = buildpath(n->str, realname, perm);
contents = readpackage(err, path, realname, perm);
path = cuttail(path, 1); // remove null terminator
}
if (!contents.s) {
prints8(err, S("pkg-config: "));
prints8(err, S("could not find package '"));
prints8(err, realname);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
parseresult r = parsepackage(contents, perm);
switch (r.err) {
case parse_DUPVARABLE:
prints8(err, S("pkg-config: "));
prints8(err, S("duplicate variable '"));
prints8(err, r.dupname);
prints8(err, S("' in '"));
prints8(err, path);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
case parse_DUPFIELD:
prints8(err, S("pkg-config: "));
prints8(err, S("duplicate field '"));
prints8(err, r.dupname);
prints8(err, S("' in '"));
prints8(err, path);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
case parse_OK:
break;
}
r.pkg.path = path;
r.pkg.realname = realname;
s8 pcfiledir = s8pathencode(dirname(path), perm);
*insert(&r.pkg.env, S("pcfiledir"), perm) = pcfiledir;
s8 missing = {0};
if (!r.pkg.name.s) {
missing = S("Name");
} else if (!r.pkg.version.s) {
missing = S("Version");
} else if (!r.pkg.description.s) {
missing = S("Description");
}
if (missing.s) {
prints8(err, S("pkg-config: "));
prints8(err, S("missing field '"));
prints8(err, missing);
prints8(err, S("' in '"));
prints8(err, r.pkg.path);
prints8(err, S("'\n"));
flush(err);
#ifndef FUZZTEST
// Do not enforce during fuzzing
os_fail(err->ctx);
#endif
}
return r.pkg;
}
typedef struct {
s8 arg;
s8 tail;
b32 ok;
} dequoted;
// Matches pkg-config's listing, which excludes "$()", but also match
// pathencode()ed bytes for escaping, which handles "$()" in paths.
static b32 shellmeta(u8 c)
{
s8 meta = S("\"!#%&'*<>?[\\]`{|}");
for (iz i = 0; i < meta.len; i++) {
if (meta.s[i]==c || pathdecode(c)!=c) {
return 1;
}
}
return 0;
}
// Process the next token. Return it and the unprocessed remainder.
static dequoted dequote(s8 s, arena *perm)
{
iz i = 0;
u8 quote = 0;
b32 escaped = 0;
dequoted r = {0};
arena rollback = *perm;
u8buf mem = newmembuf(perm);
for (; s.len && whitespace(*s.s); s = cuthead(s, 1)) {}
for (i = 0; i < s.len; i++) {
u8 c = s.s[i];
if (whitespace(c)) {
c = ' ';
}
u8 decoded = pathdecode(c);
if (quote == '\'') {
if (c == '\'') {
quote = 0;
} else if (c==' ' || shellmeta(c)) {
printu8(&mem, '\\');
printu8(&mem, decoded);
} else {
printu8(&mem, decoded);
}
} else if (quote == '"') {
if (escaped) {
escaped = 0;
if (c!='\\' && c!='"') {
printu8(&mem, '\\');
if (c==' ' || shellmeta(c)) {
printu8(&mem, '\\');
}
}
printu8(&mem, decoded);
} else if (c == '\"') {
quote = 0;
} else if (c==' ' || shellmeta(c)) {
printu8(&mem, '\\');
printu8(&mem, decoded);
} else {
escaped = c == '\\';
printu8(&mem, decoded);
}
} else if (c=='\'' || c=='"') {
quote = c;
} else if (shellmeta(c)) {
printu8(&mem, '\\');
printu8(&mem, decoded);
} else if (c==' ') {
break;
} else {
printu8(&mem, c);
}
}
if (quote) {
*perm = rollback;
return r;
}
r.arg = finalize(&mem);
r.tail = cuthead(s, i);
r.ok = 1;
return r;
}
// Compare version strings, returning [-1, 0, +1]. Follows the RPM
// version comparison specification like the original pkg-config.
static i32 compareversions(s8 va, s8 vb)
{
iz i = 0;
while (i b) {
return +1;
}
i++;
} else {
s8pair pa = digits(cuthead(va, i));
s8pair pb = digits(cuthead(vb, i));
if (pa.head.len < pb.head.len) {
return -1;
} else if (pa.head.len > pb.head.len) {
return +1;
}
for (i = 0; i < pa.head.len; i++) {
a = pa.head.s[i];
b = pb.head.s[i];
if (a < b) {
return -1;
} else if (a > b) {
return +1;
}
}
va = pa.tail;
vb = pb.tail;
i = 0;
}
}
if (va.len < vb.len) {
return -1;
} else if (va.len > vb.len) {
return +1;
}
return 0;
}
typedef struct {
pkgspec *specs;
pkg *newpkg;
i32 depth;
i32 flags;
} procstate;
typedef struct {
u8buf *err;
search search;
env **global;
i32 maxdepth;
b32 define_prefix;
b32 recursive;
b32 ignore_versions;
procstate stack[256];
} processor;
static processor *newprocessor(config *c, u8buf *err, env **g)
{
arena *perm = &c->perm;
processor *proc = new(perm, processor, 1);
proc->err = err;
proc->search = newsearch(c->delim);
appendpath(&proc->search, c->envpath, perm);
appendpath(&proc->search, c->fixedpath, perm);
proc->global = g;
proc->maxdepth = (u32)-1 >> 1;
proc->define_prefix = 1;
proc->recursive = 1;
return proc;
}
static void setprefix(pkg *p, arena *perm)
{
s8 parent = dirname(p->path);
if (s8equals(S("pkgconfig"), basename(parent))) {
s8 prefix = dirname(dirname(parent));
prefix = s8pathencode(prefix, perm);
*insert(&p->env, S("prefix"), perm) = prefix;
}
}
static void failmaxrecurse(u8buf *err, s8 tok)
{
prints8(err, S("pkg-config: "));
prints8(err, S("exceeded max recursion depth on '"));
prints8(err, tok);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
static void failversion(u8buf *err, pkg *pkg, versop op, s8 want)
{
prints8(err, S("pkg-config: "));
prints8(err, S("requested '"));
prints8(err, pkg->realname);
prints8(err, S("' "));
prints8(err, opname(op));
prints8(err, S(" '"));
prints8(err, want);
prints8(err, S("' but got '"));
prints8(err, pkg->version);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
static pkgs process(processor *proc, pkgspec *specs, arena *perm)
{
u8buf *err = proc->err;
pkgs pkgs = {0};
env **global = proc->global;
search *search = &proc->search;
procstate *stack = proc->stack;
i32 cap = countof(proc->stack);
i32 top = 0;
stack[0] = (procstate){0};
stack[0].specs = specs;
stack[0].flags = pkg_DIRECT | pkg_PUBLIC;
while (top >= 0) {
procstate *s = stack + top;
if (s->newpkg) {
prepend(&pkgs, s->newpkg);
s->newpkg = 0;
}
pkgspec *spec = s->specs;
if (!spec) {
top--;
continue;
}
s->specs = spec->next;
s8 realname = pathtorealname(spec->name);
pkg *p = locate(&pkgs, realname, perm);
i32 depth = s->depth + 1;
i32 flags = s->flags;
if (p->contents.s) {
if (flags&pkg_PUBLIC && !(p->flags & pkg_PUBLIC)) {
// We're on a public branch, but this package was
// previously loaded as private. Recursively traverse
// its public requires and mark all as public.
p->flags |= pkg_PUBLIC;
if (proc->recursive && depthmaxdepth) {
if (top >= cap-1) {
failmaxrecurse(err, p->name);
}
top++;
stack[top].specs = p->specs_requires;
stack[top].depth = depth;
stack[top].flags = flags & ~pkg_DIRECT;
}
}
} else {
// Package hasn't been loaded yet, so find and load it.
s->newpkg = p;
pkg newpkg = findpackage(search, err, spec->name, perm);
if (proc->define_prefix) {
setprefix(&newpkg, perm);
}
expandmerge(err, *global, p, &newpkg, perm);
if (spec->op && !proc->ignore_versions) {
i32 cmp = compareversions(p->version, spec->version);
if (!validcompare(spec->op, cmp)) {
failversion(err, p, spec->op, spec->version);
}
}
if (proc->recursive && depthmaxdepth) {
if (top >= cap-2) {
failmaxrecurse(err, p->name);
}
top++;
stack[top].specs = p->specs_requires;
stack[top].depth = depth;
stack[top].flags = flags & ~pkg_DIRECT;
top++;
stack[top].specs = p->specs_requiresprivate;
stack[top].depth = depth;
stack[top].flags = 0;
}
}
p->flags |= flags;
}
assert(allpresent(pkgs));
return pkgs;
}
typedef enum {
filter_ANY,
filter_I,
filter_L,
filter_l,
filter_OTHERC,
filter_OTHERL
} filter;
static b32 filterok(filter f, s8 arg)
{
switch (f) {
case filter_ANY:
return 1;
case filter_I:
return startswith(arg, S("-I"));
case filter_L:
return startswith(arg, S("-L"));
case filter_l:
return startswith(arg, S("-l"));
case filter_OTHERC:
return !startswith(arg, S("-I"));
case filter_OTHERL:
return !startswith(arg, S("-L")) && !startswith(arg, S("-l"));
}
assert(0);
}
static void msvcize(u8buf *out, s8 arg)
{
if (startswith(arg, S("-L"))) {
prints8(out, S("/libpath:"));
prints8(out, cuthead(arg, 2));
} else if (startswith(arg, S("-I"))) {
prints8(out, S("/I"));
prints8(out, cuthead(arg, 2));
} else if (startswith(arg, S("-l"))) {
prints8(out, cuthead(arg, 2));
prints8(out, S(".lib"));
} else if (startswith(arg, S("-D"))) {
prints8(out, S("/D"));
prints8(out, cuthead(arg, 2));
} else if (s8equals(arg, S("-mwindows"))) {
prints8(out, S("/subsystem:windows"));
} else if (s8equals(arg, S("-mconsole"))) {
prints8(out, S("/subsystem:console"));
} else {
prints8(out, arg);
}
}
typedef struct argpos argpos;
struct argpos {
argpos *child[4];
argpos *next;
s8 arg;
iz position;
};
typedef struct {
s8list list;
argpos *positions;
iz count;
} args;
static argpos *findargpos(argpos **m, s8 arg, arena *perm)
{
for (u32 h = s8hash(arg); *m; h <<= 2) {
if (s8equals((*m)->arg, arg)) {
return *m;
}
m = &(*m)->child[h>>30];
}
if (perm) {
*m = new(perm, argpos, 1);
(*m)->arg = arg;
}
return *m;
}
static b32 dedupable(s8 arg)
{
// Do not count "-I" or "-L" with detached argument
if (arg.len<3 || arg.s[0]!='-') {
return 0;
} else if (s8equals(arg, S("-pthread"))) {
return 1;
}
s8 flags = S("DILflm");
for (iz i = 0; i < flags.len; i++) {
if (arg.s[1] == flags.s[i]) {
return 1;
}
}
return 0;
}
static void appendarg(args *args, s8 arg, arena *perm)
{
append(&args->list, arg, perm);
iz position = args->count++;
if (dedupable(arg)) {
argpos *n = findargpos(&args->positions, arg, perm);
if (!n->position || startswith(arg, S("-l"))) {
// Zero position reserved for null, so bias it by 1
n->position = 1 + position;
}
}
}
static void excludearg(args *args, s8 arg, arena *perm)
{
argpos *n = findargpos(&args->positions, arg, perm);
n->position = -1; // i.e. position before first argument
}
// Is this the correct position for the given argument?
static b32 inposition(args *args, s8 arg, iz position)
{
argpos *n = findargpos(&args->positions, arg, 0);
return !n || n->position==position+1;
}
typedef struct {
arena *perm;
iz *argcount;
args args;
filter filter;
b32 msvc;
u8 delim;
} fieldwriter;
static fieldwriter newfieldwriter(filter f, iz *argcount, arena *perm)
{
fieldwriter w = {0};
w.perm = perm;
w.filter = f;
w.argcount = argcount;
return w;
}
static void insertsyspath(fieldwriter *w, s8 path, u8 delim, u8 flag)
{
arena *perm = w->perm;
u8 flagbuf[3] = {0};
flagbuf[0] = '-';
flagbuf[1] = flag;
s8 prefix = S(flagbuf);
while (path.len) {
cut c = s8cut(path, delim);
s8 dir = c.head;
path = c.tail;
if (!dir.len) {
continue;
}
// Prepend option flag
dir = s8pathencode(dir, perm);
s8 syspath = news8(perm, prefix.len+dir.len);
s8copy(s8copy(syspath, prefix), dir);
// Process as an argument, as though being printed
dequoted dr = dequote(syspath, perm);
syspath = dr.arg;
// NOTE(NRK): Technically, the path doesn't need to follow the flag
// immediately e.g `-I /usr/include` (notice the space between -I and
// the include dir!).
//
// But I have not seen a single pc file that does this and so we're not
// handling this edge-case. As a proof that this should be fine in
// practice, `pkgconf` which is used by many distros, also doesn't
// handle it.
if (dr.ok && !dr.tail.len) {
excludearg(&w->args, syspath, perm);
}
}
}
static void appendfield(u8buf *err, fieldwriter *w, pkg *p, s8 field)
{
arena *perm = w->perm;
filter f = w->filter;
while (field.len) {
dequoted r = dequote(field, perm);
if (!r.ok) {
prints8(err, S("pkg-config: "));
prints8(err, S("unmatched quote in '"));
prints8(err, p->realname);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
if (filterok(f, r.arg)) {
appendarg(&w->args, r.arg, perm);
}
field = r.tail;
}
}
static void writeargs(u8buf *out, fieldwriter *w)
{
iz position = 0;
u8 delim = w->delim ? w->delim : ' ';
for (s8node *n = w->args.list.head; n; n = n->next) {
s8 arg = n->str;
if (inposition(&w->args, arg, position++)) {
if ((*w->argcount)++) {
printu8(out, delim);
}
if (w->msvc) {
msvcize(out, arg);
} else {
prints8(out, arg);
}
}
}
}
static void list(u8buf *out, u8buf *err, env *g, arena a, s8node *dirs, b32 all)
{
for (s8node *dir = dirs; dir; dir = dir->next) {
arena scratch = a;
u8buf buf = newmembuf(&scratch);
prints8(&buf, dir->str);
printu8(&buf, 0);
s8 pathz = finalize(&buf);
s8node *files = os_listing(a.ctx, &scratch, pathz);
for (s8node *file = files; file; file = file->next) {
arena temp = scratch;
s8 name = file->str;
if (name.len > 3) {
name = cuttail(name, 3); // remove extension
}
s8 path = buildpath(dir->str, name, &temp);
filemap m = os_mapfile(a.ctx, &temp, path);
if (m.status != filemap_OK) {
continue;
}
parseresult r = parsepackage(m.data, &temp);
if (r.err != parse_OK) {
continue;
}
prints8(out, name);
if (all) {
// NOTE: pkgconf does not correctly format Unicode names
// in this 30-column field, so we won't either.
for (iz i = name.len; i < 30; i++) {
printu8(out, ' ');
}
printu8(out, ' ');
expand(out, err, g, &r.pkg, r.pkg.name);
prints8(out, S(" - "));
expand(out, err, g, &r.pkg, r.pkg.description);
}
printu8(out, '\n');
}
}
}
static i32 parseuint(s8 s, i32 hi)
{
i32 v = 0;
for (iz i = 0; i < s.len; i++) {
u8 c = s.s[i];
if (digit(c)) {
v = v*10 + c - '0';
if (v >= hi) {
return hi;
}
}
}
return v;
}
static void uconfig(config *conf)
{
arena *perm = &conf->perm;
env *global = 0;
filter filterc = filter_ANY;
filter filterl = filter_ANY;
u8buf *out = newfdbuf(perm, 1, 1<<12);
u8buf *err = newfdbuf(perm, 2, 1<<7);
processor *proc = newprocessor(conf, err, &global);
iz argcount = 0;
b32 msvc = 0;
b32 libs = 0;
b32 cflags = 0;
b32 err_to_stdout = 0;
b32 silent = 0;
b32 static_ = 0;
u8 argdelim = ' ';
b32 modversion = 0;
versop override_op = versop_ERR;
s8 override_version = {0};
b32 print_sysinc = !!conf->print_sysinc.s;
b32 print_syslib = !!conf->print_syslib.s;
s8 variable = {0};
enum { list_NONE, list_ALL, list_NAMES };
i32 listing = list_NONE;
proc->define_prefix = conf->define_prefix;
s8 top_builddir = conf->top_builddir;
if (top_builddir.s) {
top_builddir = s8pathencode(top_builddir, perm);
} else {
top_builddir = S("$(top_builddir)");
}
*insert(&global, S("pc_path"), perm) = conf->pc_path;
*insert(&global, S("pc_system_includedirs"), perm) = conf->pc_sysincpath;
*insert(&global, S("pc_system_libdirs"), perm) = conf->pc_syslibpath;
*insert(&global, S("pc_sysrootdir"), perm) = S("/");
*insert(&global, S("pc_top_builddir"), perm) = top_builddir;
s8 *origargs = new(perm, s8, conf->nargs);
for (i32 i = 0; i < conf->nargs; i++) {
origargs[i] = s8fromcstr(conf->args[i]);
}
s8 *args = new(perm, s8, conf->nargs);
iz nargs = 0;
for (options opts = newoptions(origargs, conf->nargs);;) {
optresult r = nextoption(&opts);
if (!r.ok) {
break;
}
if (!r.isoption) {
args[nargs++] = r.arg;
} else if (s8equals(r.arg, S("h")) || s8equals(r.arg, S("-help"))) {
usage(out);
flush(out);
return;
} else if (s8equals(r.arg, S("-version"))) {
prints8(out, S(VERSION));
printu8(out, '\n');
flush(out);
return;
} else if (s8equals(r.arg, S("-modversion"))) {
modversion = 1;
proc->recursive = 0;
} else if (s8equals(r.arg, S("-define-prefix"))) {
proc->define_prefix = 1;
} else if (s8equals(r.arg, S("-dont-define-prefix"))) {
proc->define_prefix = 0;
} else if (s8equals(r.arg, S("-cflags"))) {
cflags = 1;
filterc = filter_ANY;
} else if (s8equals(r.arg, S("-libs"))) {
libs = 1;
filterl = filter_ANY;
} else if (s8equals(r.arg, S("-variable"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
variable = r.value;
} else if (s8equals(r.arg, S("-static"))) {
static_ = 1;
} else if (s8equals(r.arg, S("-libs-only-L"))) {
libs = 1;
filterl = filter_L;
} else if (s8equals(r.arg, S("-libs-only-l"))) {
libs = 1;
filterl = filter_l;
} else if (s8equals(r.arg, S("-libs-only-other"))) {
libs = 1;
filterl = filter_OTHERL;
} else if (s8equals(r.arg, S("-cflags-only-I"))) {
cflags = 1;
filterc = filter_I;
} else if (s8equals(r.arg, S("-cflags-only-other"))) {
cflags = 1;
filterc = filter_OTHERC;
} else if (s8equals(r.arg, S("-with-path"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
prependpath(&proc->search, r.value, perm);
} else if (s8equals(r.arg, S("-maximum-traverse-depth"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
proc->maxdepth = parseuint(r.value, 1000);
} else if (s8equals(r.arg, S("-msvc-syntax"))) {
msvc = 1;
} else if (s8equals(r.arg, S("-define-variable"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
cut c = s8cut(r.value, '=');
if (!c.ok) {
prints8(err, S("pkg-config: "));
prints8(err, S("value missing in --define-variable for '"));
prints8(err, r.value);
prints8(err, S("'\n"));
flush(err);
os_fail(err->ctx);
}
*insert(&global, c.head, perm) = c.tail;
} else if (s8equals(r.arg, S("-newlines"))) {
argdelim = '\n';
} else if (s8equals(r.arg, S("-exists"))) {
// The check already happens, just disable the messages
silent = 1;
} else if (s8equals(r.arg, S("-atleast-pkgconfig-version"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
return; // always succeeds
} else if (s8equals(r.arg, S("-atleast-version"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
override_op = versop_GTE;
override_version = r.value;
silent = 1;
proc->recursive = 0;
proc->ignore_versions = 1;
} else if (s8equals(r.arg, S("-exact-version"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
override_op = versop_EQ;
override_version = r.value;
silent = 1;
proc->recursive = 0;
proc->ignore_versions = 1;
} else if (s8equals(r.arg, S("-max-version"))) {
if (!r.value.s) {
r.value = getargopt(err, &opts, r.arg);
}
override_op = versop_LTE;
override_version = r.value;
silent = 1;
proc->recursive = 0;
proc->ignore_versions = 1;
} else if (s8equals(r.arg, S("-silence-errors"))) {
silent = 1;
} else if (s8equals(r.arg, S("-errors-to-stdout"))) {
err_to_stdout = 1;
} else if (s8equals(r.arg, S("-print-errors"))) {
// Ignore
} else if (s8equals(r.arg, S("-short-errors"))) {
// Ignore
} else if (s8equals(r.arg, S("-uninstalled"))) {
// Ignore
} else if (s8equals(r.arg, S("-keep-system-cflags"))) {
print_sysinc = 1;
} else if (s8equals(r.arg, S("-keep-system-libs"))) {
print_syslib = 1;
} else if (s8equals(r.arg, S("-validate"))) {
silent = 1;
proc->recursive = 0;
} else if (s8equals(r.arg, S("-list-all"))) {
if (!conf->haslisting) {
prints8(err, S("pkg-config: "));
prints8(err, S("--list-all is unimplemented\n"));
flush(err);
os_fail(err->ctx);
}
listing = list_ALL;
} else if (s8equals(r.arg, S("-list-package-names"))) {
if (!conf->haslisting) {
prints8(err, S("pkg-config: "));
prints8(err, S("--list-package-names is unimplemented\n"));
flush(err);
os_fail(err->ctx);
}
listing = list_NAMES;
} else {
prints8(err, S("pkg-config: "));
prints8(err, S("unknown option -"));
prints8(err, r.arg);
prints8(err, S("\n"));
flush(err);
os_fail(err->ctx);
}
}
if (err_to_stdout) {
proc->err = err = out;
}
if (silent) {
proc->err = err = newnullout(perm);
}
if (listing) {
s8node *dirs = proc->search.list.head;
list(out, err, global, *perm, dirs, listing==list_ALL);
flush(out);
return;
}
pkgspec *specs = parsespecs(args, nargs, 0, err, perm);
pkgs pkgs = process(proc, specs, perm);
if (!pkgs.count) {
prints8(err, S("pkg-config: "));
prints8(err, S("requires at least one package name\n"));
flush(err);
os_fail(err->ctx);
}
// --{atleast,exact,max}-version
if (override_op) {
for (pkg *p = pkgs.head; p; p = p->list) {
i32 cmp = compareversions(p->version, override_version);
if (!validcompare(override_op, cmp)) {
failversion(err, p, override_op, override_version);
}
}
}
if (modversion) {
for (pkg *p = pkgs.head; p; p = p->list) {
if (p->flags & pkg_DIRECT) {
prints8(out, p->version);
prints8(out, S("\n"));
}
}
}
if (variable.s) {
for (pkg *p = pkgs.head; p; p = p->list) {
if (p->flags & pkg_DIRECT) {
s8 value = lookup(global, p->env, variable);
if (value.s) {
expand(out, err, global, p, value);
prints8(out, S("\n"));
}
}
}
}
if (cflags) {
arena scratch = *perm;
fieldwriter fw = newfieldwriter(filterc, &argcount, &scratch);
fw.delim = argdelim;
fw.msvc = msvc;
if (!print_sysinc) {
insertsyspath(&fw, conf->sys_incpath, conf->delim, 'I');
}
for (pkg *p = pkgs.head; p; p = p->list) {
appendfield(err, &fw, p, p->cflags);
if (static_) {
appendfield(err, &fw, p, p->cflagsprivate);
}
}
writeargs(out, &fw);
}
if (libs) {
arena scratch = *perm;
fieldwriter fw = newfieldwriter(filterl, &argcount, &scratch);
fw.delim = argdelim;
fw.msvc = msvc;
if (!print_syslib) {
insertsyspath(&fw, conf->sys_libpath, conf->delim, 'L');
}
for (pkg *p = pkgs.head; p; p = p->list) {
if (static_) {
appendfield(err, &fw, p, p->libs);
appendfield(err, &fw, p, p->libsprivate);
} else if (p->flags & pkg_PUBLIC) {
appendfield(err, &fw, p, p->libs);
}
}
writeargs(out, &fw);
}
if (cflags || libs) {
prints8(out, S("\n"));
}
flush(out);
}
u-config-0.34.0/u-config.1 0000664 0000000 0000000 00000003446 15002525747 0015114 0 ustar 00root root 0000000 0000000 .Dd 2024-08-22
.Dt U-CONFIG 1
.Os
.
.Sh NAME
.Nm u-config
.Nd smaller, simpler, portable pkg-config clone
.
.Sh SYNOPSIS
.Nm
.Op Ar options
.Ar package Op Ar package ...
.
.Sh DESCRIPTION
.Nm
is a small, highly portable pkg-config and pkgconf clone. It retrieves compiler
and linker flags, version, and other dependency information of software
packages.
.Pp
Package metadata is retrieved from
.Dq .pc
text files, located in a system-specific path. You can override the default
search path with environment variables like
.Ev PKG_CONFIG_PATH .
See
.Sx ENVIRONMENT
for more information.
.Pp
This manual page describes the
.Nm
utility in particular. For general information about pkg-config concepts and its
file format, refer to manual pages provided by other projects, like
.Xr pkgconf 1
and
.Xr pc 5 .
.Pp
.Nm
supports a subset of the options of other pkg-config implementations. For a
list of supported options, use the
.Fl \-help
option. Some of them are unique to
.Nm :
.Bl -tag -width indent
.It Fl \-newlines
Separate arguments by line feeds instead of spaces, which is sometimes useful
like in the fish shell or when manually examining output.
.It Fl \-msvc\-syntax
Translates and prints compiler arguments into a syntax compatible with the MSVC
compiler. For example,
.Fl l Ar libname
becomes
.Cm libname.lib .
.El
.
.Sh ENVIRONMENT
.Nm
supports all the important pkg-config run-time environment variables. To see
which are supported, use the
.Fl \-help
option.
.
.Sh EXAMPLES
.Bd -literal
$ u-config --cflags --libs foo
-I/usr/include/foo -DFOO_HAS_BAR=1 -lfoo
.Ed
.
.Sh SEE ALSO
.Xr pkgconf 1 ,
.Xr pc 5
.
.Sh AUTHORS
.Nm
was written by
.An Chris Wellons Aq Mt wellons@nullprogram.com .
This manual page was written by
.An Andrea Pappacoda Aq Mt andrea@pappacoda.it
for the Debian project
.Pq and may be used by others .
u-config-0.34.0/wasi-demo.html 0000664 0000000 0000000 00000017222 15002525747 0016073 0 ustar 00root root 0000000 0000000
u-config (WASM)