pax_global_header00006660000000000000000000000064147631315400014516gustar00rootroot0000000000000052 comment=d849116da0ae5c38324adfbe1ae9d9b7b177e89d u-config-0.33.3/000077500000000000000000000000001476313154000133135ustar00rootroot00000000000000u-config-0.33.3/Makefile000066400000000000000000000061051476313154000147550ustar00rootroot00000000000000CROSS = x86_64-w64-mingw32- CC = gcc OPT = -Os PC = pkg-config # e.g. for CROSS-pkg-config DEBUG_CFLAGS = -g3 -Wall -Wextra -Wconversion -Wno-sign-conversion \ -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 pkg-config.exe: win32_main.c cmdline.c miniwin32.h u-config.c $(CROSS)$(CC) $(OPT) $(WIN32_CFLAGS) -o $@ win32_main.c $(WIN32_LIBS) pkg-config-debug.exe: win32_main.c cmdline.c miniwin32.h u-config.c $(CROSS)$(CC) -nostartfiles $(DEBUG_CFLAGS) -o $@ win32_main.c # Auto-configure using the system's pkg-config search path pkg-config: generic_main.c u-config.c $(CC) $(OPT) -o $@ generic_main.c \ -DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\"" pkg-config-debug: generic_main.c u-config.c $(CC) $(DEBUG_CFLAGS) -o $@ generic_main.c # Auto-configure using the system's pkg-config search path pkg-config-linux-amd64: linux_amd64_main.c linux_noarch.c u-config.c $(CC) $(OPT) $(LINUX_CFLAGS) -o $@ linux_amd64_main.c $(LINUX_LIBS) \ -DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\"" pkg-config-linux-amd64-debug: linux_amd64_main.c linux_noarch.c u-config.c $(CC) -nostdlib -fno-builtin $(DEBUG_CFLAGS) -o $@ linux_amd64_main.c # Auto-configure using the system's pkg-config search path pkg-config-linux-i686: linux_i686_main.c linux_noarch.c u-config.c $(CC) $(OPT) $(LINUX_CFLAGS) -o $@ linux_i686_main.c $(LINUX_LIBS) \ -DPKG_CONFIG_LIBDIR="\"$$($(PC) --variable pc_path pkg-config)\"" pkg-config-linux-i686-debug: linux_i686_main.c linux_noarch.c u-config.c $(CC) -nostdlib -fno-builtin $(DEBUG_CFLAGS) -o $@ linux_i686_main.c # Concatenate Windows-only u-config into a single source file amalgamation: pkg-config.c pkg-config.c: u-config.c miniwin32.h cmdline.c win32_main.c awk 'n{print"";n=0} NR==3{printf"%s\n",cc} !/^#i.*"/{print}' \ cc='// $$ cc -nostartfiles -o pkg-config.exe pkg-config.c' \ >$@ u-config.c n=1 miniwin32.h n=1 cmdline.c n=1 win32_main.c release: version=$$(git describe); prefix=u-config-$${version#v}; \ git archive --prefix=$$prefix/ HEAD | gzip -9 >$$prefix.tar.gz tests.exe: test_main.c u-config.c $(CROSS)$(CC) $(DEBUG_CFLAGS) -Wno-clobbered -o $@ test_main.c tests: test_main.c u-config.c $(CC) $(DEBUG_CFLAGS) -Wno-clobbered -o $@ test_main.c check test: tests$(EXE) ./tests$(EXE) # Build and install into w64devkit install: win32_main.c cmdline.c miniwin32.h u-config.c $(CROSS)$(CC) $(OPT) $(WIN32_CFLAGS) win32_main.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 \ *.ilk *.obj *.pdb test_main.exe u-config-0.33.3/README.md000066400000000000000000000170431476313154000145770ustar00rootroot00000000000000# 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. 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. The "generic" platform is libc so it works everywhere, but inherits the target's libc limitations (restricted path and environment variable access, no automatic self-configuration, etc.). $ cc -Os -o pkg-config generic_main.c However, one of core goals is to be a reliable, native pkg-config for Windows, so it has a dedicated platform layer. This layer understands Unicode paths and environment variables — though keep in mind that it's probably interacting with tools that do not. It outputs arguments encoded in UTF-8 regardless of the system code page. Do not link a C runtime (CRT) in this configuration. $ cc -Os -nostartfiles -o pkg-config win32_main.c Or with MSVC: $ cl /GS- /O2 /Os /Fe:pkg-config msvc_main.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 ### Generic configuration options The "generic" 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 `linux_*_main.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 generic 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 PLATFORM_main.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 `test_main.c` as a platform, or use the suggested test configuration in the Makefile (set `EXE=.exe` on Windows): $ make check ### Fuzz testing `fuzz_main.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 u-config-0.33.3/UNLICENSE000066400000000000000000000022731476313154000145670ustar00rootroot00000000000000This 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.33.3/cmdline.c000066400000000000000000000114001476313154000150660ustar00rootroot00000000000000#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.33.3/fuzz_main.c000066400000000000000000000022631476313154000154640ustar00rootroot00000000000000// afl fuzz test platform layer for u-config // $ afl-gcc-fast -fsanitize=undefined -fsanitize-trap fuzz_main.c // $ afl-fuzz -i /usr/share/pkgconfig -o fuzzout ./a.out #define FUZZTEST #include "u-config.c" #include #include #include // required by afl __AFL_FUZZ_INIT(); static jmp_buf ret; static s8 pcfile; static void os_fail(void) { longjmp(ret, 1); } static void os_write(int fd, s8 s) { (void)fd; (void)s; } static filemap os_mapfile(arena *perm, s8 path) { (void)perm; (void)path; filemap r = {0}; r.data = pcfile; r.status = filemap_OK; return r; } int main(void) { __AFL_INIT(); s8 args[] = {S("--static"), S("--cflags"), S("--libs"), S("afl")}; size cap = 1<<16; arena perm = {0}; perm.beg = malloc(cap); perm.end = perm.beg + cap; pcfile.s = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { pcfile.len = __AFL_FUZZ_TESTCASE_LEN; config conf = {0}; conf.perm = perm; conf.args = args; conf.nargs = countof(args); conf.fixedpath = S("/usr/lib/pkgconfig"); if (!setjmp(ret)) { uconfig(&conf); } } } u-config-0.33.3/generic_main.c000066400000000000000000000077211476313154000161060ustar00rootroot00000000000000// Generic C platform layer for u-config // This is free and unencumbered software released into the public domain. #define _CRT_SECURE_NO_WARNINGS #ifndef __PTRDIFF_TYPE__ // not GCC-like? # include # define __PTRDIFF_TYPE__ ptrdiff_t # define __builtin_unreachable() *(volatile int *)0 = 0 # define __attribute(x) #endif #include "u-config.c" #include #include #include #if _WIN32 # define PATHDELIM ";" # ifndef PKG_CONFIG_DEFINE_PREFIX # define PKG_CONFIG_DEFINE_PREFIX 1 # endif #else # define PATHDELIM ":" # ifndef PKG_CONFIG_DEFINE_PREFIX # define PKG_CONFIG_DEFINE_PREFIX 0 # endif #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" PATHDELIM "/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 PATHDELIM #endif #ifdef PKG_CONFIG_LIBDIR PKG_CONFIG_LIBDIR #else PKG_CONFIG_PREFIX "/local/lib/pkgconfig" PATHDELIM PKG_CONFIG_PREFIX "/local/share/pkgconfig" PATHDELIM PKG_CONFIG_PREFIX "/lib/pkgconfig" PATHDELIM PKG_CONFIG_PREFIX "/share/pkgconfig" #endif ; static s8 fromcstr_(char *z) { s8 s = {0}; s.s = (u8 *)z; s.len = z ? strlen(z) : 0; return s; } static arena newarena_(void) { size cap = (size)1<<22; arena a = {0}; a.beg = malloc(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; return conf; } int main(int argc, char **argv) { config *conf = newconfig_(); conf->delim = PATHDELIM[0]; conf->define_prefix = PKG_CONFIG_DEFINE_PREFIX; if (argc) { argc--; argv++; } conf->args = new(&conf->perm, s8, argc); conf->nargs = argc; for (int i = 0; i < argc; i++) { conf->args[i] = fromcstr_(argv[i]); } 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 = fromcstr_(getenv("PKG_CONFIG_PATH")); conf->fixedpath = fromcstr_(getenv("PKG_CONFIG_LIBDIR")); if (!conf->fixedpath.s) { conf->fixedpath = S(pkg_config_path); } conf->sys_incpath = fromcstr_(getenv("PKG_CONFIG_SYSTEM_INCLUDE_PATH")); if (!conf->sys_incpath.s) { conf->sys_incpath = S(PKG_CONFIG_SYSTEM_INCLUDE_PATH); } conf->sys_libpath = fromcstr_(getenv("PKG_CONFIG_SYSTEM_LIBRARY_PATH")); if (!conf->sys_libpath.s) { conf->sys_libpath = S(PKG_CONFIG_SYSTEM_LIBRARY_PATH); } conf->top_builddir = fromcstr_(getenv("PKG_CONFIG_TOP_BUILD_DIR")); conf->print_sysinc = fromcstr_(getenv("PKG_CONFIG_ALLOW_SYSTEM_CFLAGS")); conf->print_syslib = fromcstr_(getenv("PKG_CONFIG_ALLOW_SYSTEM_LIBS")); uconfig(conf); return ferror(stdout); } static filemap os_mapfile(arena *perm, s8 path) { assert(path.len > 0); assert(!path.s[path.len-1]); filemap r = {0}; FILE *f = fopen((char *)path.s, "rb"); if (!f) { r.status = filemap_NOTFOUND; return r; } r.data.s = (u8 *)perm->beg; size available = perm->end - perm->beg; r.data.len = fread(r.data.s, 1, available, f); fclose(f); if (r.data.len == available) { // 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 void os_write(int fd, s8 s) { assert(fd==1 || fd==2); FILE *f = fd==1 ? stdout : stderr; fwrite(s.s, s.len, 1, f); fflush(f); } static void os_fail(void) { exit(1); } u-config-0.33.3/linux_amd64_main.c000066400000000000000000000025631476313154000166230ustar00rootroot00000000000000// libc-free platform layer for x86-64 Linux // This is free and unencumbered software released into the public domain. #include "u-config.c" #define SYS_close 3 #define SYS_exit 60 #define SYS_mmap 9 #define SYS_open 2 #define SYS_read 0 #define SYS_write 1 #include "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; } static long syscall6(long n, long a, long b, long c, long d, long e, long f) { long r; register long r10 asm("r10") = d; register long r8 asm("r8") = e; register long r9 asm("r9") = f; asm volatile ( "syscall" : "=a"(r) : "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory" ); return r; } u-config-0.33.3/linux_i686_main.c000066400000000000000000000026201476313154000163760ustar00rootroot00000000000000// libc-free platform layer for x86-32 Linux // This is free and unencumbered software released into the public domain. #include "u-config.c" #define SYS_close 6 #define SYS_exit 1 #define SYS_mmap 192 // actually mmap2 #define SYS_open 5 #define SYS_read 3 #define SYS_write 4 #include "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; } static long syscall6(long n, long a, long b, long c, long d, long e, long f) { long r; asm volatile ( "push %7\n" "push %%ebp\n" "mov 4(%%esp), %%ebp\n" "int $0x80\n" "pop %%ebp\n" "add $4, %%esp\n" : "=a"(r) : "a"(n), "b"(a), "c"(b), "d"(c), "S"(d), "D"(e), "m"(f) : "memory" ); return r; } u-config-0.33.3/linux_noarch.c000066400000000000000000000107621476313154000161560ustar00rootroot00000000000000// 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_mmap, SYS_exit. Arch-specific _start calls the 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 long syscall6(long, long, long, long, long, long, long); static void os_fail(void) { syscall1(SYS_exit, 1); __builtin_unreachable(); } static void os_write(i32 fd, s8 s) { assert(fd==1 || fd==2); while (s.len) { long r = syscall3(SYS_write, fd, (long)s.s, s.len); if (r < 0) { os_fail(); } s = cuthead(s, (size)r); } } static filemap os_mapfile(arena *perm, s8 path) { 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; size 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 s8 fromcstr_(u8 *z) { s8 s = {0}; s.s = (u8 *)z; if (s.s) { for (; s.s[s.len]; s.len++) {} } return s; } static arena newarena_(void) { arena a = {0}; size cap = 1<<22; unsigned long p = syscall6(SYS_mmap, 0, cap, 3, 0x22, -1, 0); if (p > -4096UL) { a.beg = (byte *)16; // aligned, non-null, zero-size arena cap = 0; } else { a.beg = (byte *)p; } a.end = a.beg + cap; return a; } static config *newconfig_(void) { arena perm = newarena_(); config *conf = new(&perm, config, 1); conf->perm = perm; 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 = new(&conf->perm, s8, argc); for (i32 i = 0; i < argc; i++) { conf->args[i] = fromcstr_(argv[i]); } 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(fromcstr_(*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.33.3/miniwin32.h000066400000000000000000000022161476313154000153040ustar00rootroot00000000000000// Win32 types, constants, and declarations (replaces windows.h) // This is free and unencumbered software released into the public domain. typedef __PTRDIFF_TYPE__ iptr; typedef __SIZE_TYPE__ 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, }; #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(c16 *) GetCommandLineW(void); W32(b32) GetConsoleMode(iptr, i32 *); W32(i32) GetEnvironmentVariableW(c16 *, c16 *, i32); W32(i32) GetModuleFileNameW(iptr, c16 *, i32); W32(i32) GetStdHandle(i32); W32(b32) ReadFile(iptr, u8 *, i32, i32 *, uptr); W32(byte *) VirtualAlloc(uptr, size, i32, i32); W32(b32) WriteConsoleW(iptr, c16 *, i32, i32 *, uptr); W32(b32) WriteFile(iptr, u8 *, i32, i32 *, uptr); u-config-0.33.3/msvc_main.c000066400000000000000000000011241476313154000154310ustar00rootroot00000000000000// MSVC Win32 platform layer for u-config // $ cl /GS- /O2 /Os /Fe:pkg-config.exe msvc_main.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") #ifdef _WIN64 # define __PTRDIFF_TYPE__ __int64 # define __SIZE_TYPE__ unsigned __int64 #else # define __PTRDIFF_TYPE__ __int32 # define __SIZE_TYPE__ unsigned __int32 #endif #define __builtin_unreachable() __assume(0) #define __attribute(x) #include "win32_main.c" u-config-0.33.3/test_main.c000066400000000000000000000550701476313154000154510ustar00rootroot00000000000000// 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 __PTRDIFF_TYPE__ // not GCC-like? # include # define __PTRDIFF_TYPE__ ptrdiff_t # define __builtin_unreachable() *(volatile int *)0 = 0 # define __builtin_trap() *(volatile int *)0 = 0 # define __attribute(x) #endif #include "u-config.c" #include #include #include #include #define E S("") #define SHOULDPASS \ for (i32 r = setjmp(context.exit); \ !r || (r>0 && (__builtin_trap(), 0)); \ r = -1) #define SHOULDFAIL \ for (i32 r = setjmp(context.exit); !r; __builtin_trap()) #define PCHDR "Name:\n" "Version:\n" "Description:\n" #define EXPECT(w) \ if (!s8equals(context.output, S(w))) { \ printf("EXPECT: %s", w); \ printf("OUTPUT: %.*s", (int)context.output.len, context.output.s); \ fflush(stdout); \ __builtin_trap(); \ } #define MATCH(w) \ if (s8find_(context.output, S(w)) == context.output.len) { \ printf("MATCH: %s\n", w); \ printf("OUTPUT: %.*s", (int)context.output.len, context.output.s); \ fflush(stdout); \ __builtin_trap(); \ } static struct { jmp_buf exit; arena perm; arena reset; s8 outbuf; s8 output; s8 outavail; env *filesystem; b32 active; } context; static void os_fail(void) { assert(context.active); context.active = 0; longjmp(context.exit, 1); } static filemap os_mapfile(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(&context.filesystem, path, 0); if (!data) { r.status = filemap_NOTFOUND; return r; } r.data = *data; r.status = filemap_OK; return r; } static void os_write(i32 fd, s8 s) { assert(fd==1 || fd==2); assert(context.outavail.len >= s.len); context.outavail = s8copy(context.outavail, s); context.output.len += s.len; } // Return needle offset, or the length of haystack on no match. static size s8find_(s8 haystack, s8 needle) { u32 match = 0; for (size i = 0; i < needle.len; i++) { match = match*257u + needle.s[i]; } u32 f = 1; u32 x = 257; for (size n = needle.len-1; n>0; n /= 2) { f *= n&1 ? x : 1; x *= x; } size i = 0; u32 hash = 0; for (; iperm) = 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, s8, conf.nargs); va_start(ap, conf); for (size i = 0; i < conf.nargs; i++) { conf.args[i] = va_arg(ap, s8); } va_end(ap); context.output = takehead(context.outbuf, 0); context.outavail = context.outbuf; fillbytes(conf.perm.beg, 0x55, conf.perm.end-conf.perm.beg); context.active = 1; uconfig(&conf); assert(context.active); context.active = 0; } static void test_noargs(void) { // NOTE: this is mainly a sanity check of the test system itself config conf = newtest_(S("no arguments")); SHOULDFAIL { run(conf, E); } } static void test_dashdash(void) { config conf = newtest_(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(void) { config conf = newtest_(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(void) { config conf = newtest_(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(void) { // Scenario: liba depends on libc and libb // Expect: modversion order is unaffected config conf = newtest_(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(void) { config conf = newtest_(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(void) { config conf = newtest_(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_transitive(void) { // Scenario: a privately requires b which publicly requires c // Expect: --libs should not include c without --static config conf = newtest_(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(void) { // 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_(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(void) { config conf = newtest_(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(void) { // 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_(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(void) { // Scenario: liba depends on libc and libb, libb depends on libc // Expect: libc is listed last config conf = newtest_(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(void) { // 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_(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(void) { // Test if that paths allow parenthesis, but also that parenthesis // otherwise still work as meta characters. config conf = newtest_(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_error_messages(void) { // Check that error messages mention important information config conf = newtest_(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 { 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(void) { // Stresses the hash-trie-backed package environment config conf = newtest_(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); 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(void) { config conf = newtest_(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_(size cap) { arena arena = {0}; arena.beg = malloc(cap); if (!arena.beg) { __builtin_trap(); } arena.end = arena.beg + cap; return arena; } int main(void) { context.perm = context.reset = newarena_(1<<21); test_noargs(); test_dashdash(); test_modversion(); test_versioncheck(); test_versionorder(); test_overrides(); test_maximum_traverse_depth(); test_private_transitive(); test_revealed_transitive(); test_syspaths(); test_libsorder(); test_staticorder(); test_windows(); test_parens(); test_error_messages(); test_manyvars(); test_lol(); puts("all tests pass"); return 0; } u-config-0.33.3/u-config.1000066400000000000000000000034461476313154000151130ustar00rootroot00000000000000.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.33.3/u-config.c000066400000000000000000001460421476313154000151750ustar00rootroot00000000000000// 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. #define VERSION "0.33.3" typedef unsigned char u8; typedef signed int b32; typedef signed int i32; typedef unsigned int u32; typedef __PTRDIFF_TYPE__ size; typedef char byte; #define assert(c) while (!(c)) __builtin_unreachable() #define countof(a) (size)(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 { u8 *s; size len; } s8; typedef struct { byte *beg; byte *end; } arena; typedef struct { arena perm; s8 *args; size 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; 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(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(i32 fd, s8); // Immediately exit the program with a non-zero status. static void os_fail(void) __attribute((noreturn)); // Application static void oom(void) { os_write(2, S("pkg-config: out of memory\n")); os_fail(); } 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, size len) { byte *r = dst; for (; len; len--) { *dst++ = c; } return r; } static void u8copy(u8 *dst, u8 *src, size n) { assert(n >= 0); for (; n; n--) { *dst++ = *src++; } } static i32 u8compare(u8 *a, u8 *b, size 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, size objsize, size count) { assert(objsize > 0); assert(count >= 0); size alignment = -((u32)objsize * (u32)count) & 7; size available = a->end - a->beg - alignment; if (count > available/objsize) { oom(); } size total = objsize * count; return fillbytes(a->end -= total + alignment, 0, total); } static s8 news8(arena *perm, size 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; } // 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, size off) { assert(off >= 0); assert(off <= s.len); s.s += off; s.len -= off; return s; } static s8 takehead(s8 s, size len) { assert(len >= 0); assert(len <= s.len); s.len = len; return s; } static s8 cuttail(s8 s, size len) { assert(len >= 0); assert(len <= s.len); s.len -= len; return s; } static s8 taketail(s8 s, size 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 (size 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) { size len = 0; for (; lencap = cap; b->buf = new(perm, u8, cap); b->fd = fd; return b; } static u8buf *newnullout(arena *perm) { u8buf *b = new(perm, u8buf, 1); b->fd = -1; 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; 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(); break; default: if (b->len) { os_write(b->fd, gets8(b)); } } b->len = 0; } static void prints8(u8buf *b, s8 s) { if (b->fd == -1) { return; // /dev/null } for (size off = 0; off < s.len;) { size avail = b->cap - b->len; size 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) { size len = path.len; while (len>0 && !pathsep(path.s[--len])) {} return takehead(path, len); } static s8 basename(s8 path) { size 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"); size 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 10 s8 name; s8 description; s8 url; s8 version; s8 requires; s8 requiresprivate; s8 conflicts; s8 libs; s8 libsprivate; s8 cflags; }; static s8 *fieldbyid(pkg *p, i32 id) { assert(id >= 0); assert(id < PKG_NFIELDS); return (s8 *)((byte *)&p->name + id*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") }; 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; size 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) { size 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 size 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) { size len = 0; s8 c = news8(perm, s.len); for (size i = 0; i < s.len; i++) { u8 b = s.s[i]; if (b == '\\') { size 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(); } } 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(); } static pkgspec *parsespecs(s8 *args, size nargs, pkg *p, u8buf *err, arena *a) { pkgspec *head = 0; pkgspec *pkg = 0; for (size 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_DUPFIELD; return dup; } break; } // Skip leading space; newlines may be escaped with a backslash while (p < e) { if (*p == '\\') { size 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 (; pindex == p->nargs) { return r; } for (;;) { 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" " --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 s8node; struct s8node { s8node *next; s8 str; }; 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; } size baselen = 0; for (size 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, 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(); 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 (size 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(); } prints8(out, takehead(s, i)); size beg = i + 2; size 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(); } 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(); } 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(); 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(); 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(); #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 (size 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) { size 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) { size 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(); } 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(); } 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; size position; }; typedef struct { s8list list; argpos *positions; size 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 (size 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); size 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, size position) { argpos *n = findargpos(&args->positions, arg, 0); return !n || n->position==position+1; } typedef struct { arena *perm; size *argcount; args args; filter filter; b32 msvc; u8 delim; } fieldwriter; static fieldwriter newfieldwriter(filter f, size *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(); } if (filterok(f, r.arg)) { appendarg(&w->args, r.arg, perm); } field = r.tail; } } static void writeargs(u8buf *out, fieldwriter *w) { size 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 i32 parseuint(s8 s, i32 hi) { i32 v = 0; for (size 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); size 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}; 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 *args = new(perm, s8, conf->nargs); size nargs = 0; for (options opts = newoptions(conf->args, 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(); } *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 { prints8(err, S("pkg-config: ")); prints8(err, S("unknown option -")); prints8(err, r.arg); prints8(err, S("\n")); flush(err); os_fail(); } } if (err_to_stdout) { err = out; } if (silent) { err = newnullout(perm); } 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(); } // --{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); } 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.33.3/win32_main.c000066400000000000000000000320431476313154000154270ustar00rootroot00000000000000// Mingw-w64 Win32 platform layer for u-config // $ cc -nostartfiles -o pkg-config win32_main.c // This is free and unencumbered software released into the public domain. #include "u-config.c" #include "miniwin32.h" #include "cmdline.c" #ifndef PKG_CONFIG_PREFIX # define PKG_CONFIG_PREFIX #endif // For communication with os_write() static struct { i32 handle; b32 isconsole; b32 err; } handles[3]; typedef struct { c16 *s; size len; } s16; static s16 s16cuthead_(s16 s, size off) { assert(off >= 0); assert(off <= s.len); s.s += off; s.len -= off; return s; } static arena newarena_(size 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) { size 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) { size 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. size cap = (perm->end - perm->beg) / (size)sizeof(c16); if (wlen > cap) { oom(); } 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 (size i = 0; i < path.len; i++) { if (path.s[i] == '\\') { path.s[i] = '/'; } } return path; } static i32 truncsize(size 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(";"); size 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 s8 fromcstr_(u8 *z) { s8 s = {0}; s.s = z; if (s.s) { for (; s.s[s.len]; s.len++) {} } return s; } static config *newconfig_(void) { arena perm = newarena_(1<<22); config *conf = new(&perm, config, 1); conf->perm = perm; return conf; } __attribute((force_align_arg_pointer)) void mainCRTStartup(void) { config *conf = newconfig_(); conf->delim = ';'; conf->define_prefix = 1; arena *perm = &conf->perm; i32 dummy; handles[1].handle = GetStdHandle(STD_OUTPUT_HANDLE); handles[1].isconsole = GetConsoleMode(handles[1].handle, &dummy); handles[2].handle = GetStdHandle(STD_ERROR_HANDLE); handles[2].isconsole = GetConsoleMode(handles[2].handle, &dummy); u8 **argv = new(perm, u8 *, CMDLINE_ARGV_MAX); c16 *cmdline = GetCommandLineW(); conf->nargs = cmdline_to_argv8(cmdline, argv) - 1; conf->args = new(perm, s8, conf->nargs); for (size i = 0; i < conf->nargs; i++) { conf->args[i] = fromcstr_(argv[i+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(handles[1].err || handles[2].err); assert(0); } static filemap os_mapfile(arena *perm, s8 path) { 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; size 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 void os_fail(void) { ExitProcess(1); assert(0); } typedef struct { c16 buf[1<<8]; i32 len; i32 handle; 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(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 = &handles[fd].err; if (*err) { return; } i32 handle = handles[fd].handle; if (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); } }