Skip to content

Instantly share code, notes, and snippets.

@unstabler
Created April 4, 2026 08:50
Show Gist options
  • Select an option

  • Save unstabler/16413f60851484a3b6048dfd015975ab to your computer and use it in GitHub Desktop.

Select an option

Save unstabler/16413f60851484a3b6048dfd015975ab to your computer and use it in GitHub Desktop.
ppkg-config.bat - Python Core-Only replacement for pkg-config
@echo off & python -x "%~f0" %* & exit /b %errorlevel%
# -----------------------------------------------------------------------------
# ppkg-config.bat - Python Core-Only replacement for pkg-config
#
# Python 3 port implemented by Gemini 3.1 Pro
#
# Original Perl Implementation:
# Copyright (C) 2012 M. Nunberg.
# You may use and distribute this software under the same terms and
# conditions as Perl itself (Artistic License / GPL).
# -----------------------------------------------------------------------------
import sys
import os
import re
import shlex
import argparse
from glob import glob
from collections import OrderedDict
VERSION = '0.26026'
COMPAT_VERSION = VERSION[-2:]
# -----------------------------------------------------------------------------
# Sane Defaults
# -----------------------------------------------------------------------------
DEFAULT_SEARCH_PATH = [
'/usr/local/lib/pkgconfig', '/usr/local/share/pkgconfig',
'/usr/lib/pkgconfig', '/usr/share/pkgconfig'
]
DEFAULT_EXCLUDE_CFLAGS = ['-I/usr/include', '-I/usr/local/include']
DEFAULT_EXCLUDE_LFLAGS = []
for base in ['/lib', '/lib32', '/lib64', '/usr/lib', '/usr/lib32', '/usr/lib64', '/usr/local/lib']:
DEFAULT_EXCLUDE_LFLAGS.extend([f"-L{base}", f"-R{base}"])
if os.environ.get('PKG_CONFIG_NO_OS_CUSTOMIZATION'):
pass
elif os.environ.get('PKG_CONFIG_LIBDIR'):
DEFAULT_SEARCH_PATH = os.environ['PKG_CONFIG_LIBDIR'].split(os.pathsep)
else:
platform = sys.platform
if platform == 'win32':
DEFAULT_EXCLUDE_CFLAGS = []
DEFAULT_EXCLUDE_LFLAGS = []
elif platform == 'darwin':
if os.path.exists('/usr/local/Homebrew/bin/brew'):
DEFAULT_SEARCH_PATH.extend(glob('/usr/local/opt/*/lib/pkgconfig'))
env_search_path = os.environ.get('PKG_CONFIG_PATH', '').split(os.pathsep) if os.environ.get('PKG_CONFIG_PATH') else []
DEFAULT_SEARCH_PATH = env_search_path + [p for p in DEFAULT_SEARCH_PATH if p]
if os.environ.get('PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'):
DEFAULT_EXCLUDE_CFLAGS = []
if os.environ.get('PKG_CONFIG_ALLOW_SYSTEM_LIBS'):
DEFAULT_EXCLUDE_LFLAGS = []
# -----------------------------------------------------------------------------
# PkgConfig Core Class
# -----------------------------------------------------------------------------
class PkgConfig:
def __init__(self, search_path=None, use_static=False, uservars=None, exclude_cflags=None, exclude_ldflags=None, no_recurse=False):
self.search_path = search_path if search_path is not None else DEFAULT_SEARCH_PATH
self.use_static = use_static
self.no_recurse = no_recurse
self.exclude_cflags = exclude_cflags if exclude_cflags is not None else DEFAULT_EXCLUDE_CFLAGS
self.exclude_ldflags = exclude_ldflags if exclude_ldflags is not None else DEFAULT_EXCLUDE_LFLAGS
self.cflags = []
self.ldflags = []
self.libs_deplist = {}
self.recursion_level = 0
self.filevars = {}
self.uservars = uservars if uservars else {}
self.defined_variables = {}
self.pkg_exists = False
self.pkg_version = ""
self.pkg_url = ""
self.pkg_description = ""
self.errmsg = ""
def _expand_vars(self, value):
def replacer(match):
var_name = match.group(1)
val = self.filevars.get(var_name, "")
return " ".join(val) if isinstance(val, list) else val
out_value = value
while re.search(r'\$\{([a-zA-Z0-9_.]+)\}', out_value):
out_value = re.sub(r'\$\{([a-zA-Z0-9_.]+)\}', replacer, out_value)
out_value = out_value.replace('$$', '$')
return out_value
def assign_var(self, field, value, force=False):
if not force and field in self.uservars:
return
if field.endswith(('dir', 'prefix')) and '$' not in value:
self.filevars[field] = [v for v in shlex.split(value) if v]
return
expanded = self._expand_vars(value)
self.filevars[field] = [v for v in shlex.split(expanded) if v]
def parse_line(self, line):
line = line.split('#')[0].strip()
if not line: return
match = re.search(r'([=:])', line)
if not match: return
tok = match.group(1)
parts = line.split(tok, 1)
if len(parts) != 2: return
field, value = parts[0].strip(), parts[1].strip()
if tok == '=':
self.defined_variables[field] = value
field = field.lower()
if tok == ':' and not field.startswith(('cflags', 'libs')):
value = self._expand_vars(value)
self.filevars[field] = [v for v in value.split() if v]
return
field = field.replace("'", "").replace('"', "")
self.assign_var(field, value)
def get_requires(self, requires_str):
if not requires_str: return []
reqlist = requires_str.replace(',', ' ').split()
ret = []
while reqlist:
req = reqlist.pop(0)
cmp_op = want = None
match = re.search(r'([<>=]+)', req)
if match:
cmp_op = match.group(1)
if req.endswith(cmp_op):
want = reqlist.pop(0) if reqlist else ""
else:
want = req.split(cmp_op)[-1]
req = req.split(cmp_op)[0]
elif reqlist and re.match(r'[<>=]+', reqlist[0]):
cmp_op = reqlist.pop(0)
want = reqlist.pop(0) if reqlist else ""
reqlet = [req]
if cmp_op:
reqlet.extend([cmp_op, want])
ret.append(reqlet)
return ret
def parse_pcfile(self, pcfile):
try:
with open(pcfile, 'r', encoding='utf-8') as f:
text = f.read().replace('\\\n', '').replace('\\\r\n', '')
except IOError as e:
self.errmsg = f"{pcfile}: {e}"
return
for k, v in self.uservars.items():
self.assign_var(k, v, force=True)
lines = text.splitlines()
pcfiledir = os.path.dirname(pcfile).replace('\\', '/')
self.parse_line(f"pcfiledir={pcfiledir}")
for line in lines:
self.parse_line(line)
self.cflags.extend(self.filevars.get('cflags', []))
if self.use_static:
self.cflags.extend(self.filevars.get('cflags.private', []))
libs = self.filevars.get('libs', [])
if libs:
self.libs_deplist.setdefault(self.recursion_level, []).extend(libs)
if self.use_static:
libs_priv = self.filevars.get('libs.private', [])
if libs_priv:
self.libs_deplist.setdefault(self.recursion_level, []).extend(libs_priv)
deps = self.get_requires(" ".join(self.filevars.get('requires', [])))
if self.use_static:
deps.extend(self.get_requires(" ".join(self.filevars.get('requires.private', []))))
if self.recursion_level == 1 and not self.pkg_exists:
self.pkg_version = " ".join(self.filevars.get('version', []))
self.pkg_url = " ".join(self.filevars.get('url', []))
self.pkg_description = " ".join(self.filevars.get('description', []))
self.pkg_exists = True
if not self.no_recurse:
for dep_item in deps:
dep_name = dep_item[0]
other = PkgConfig(
search_path=self.search_path, use_static=self.use_static,
uservars=self.uservars, exclude_cflags=self.exclude_cflags,
exclude_ldflags=self.exclude_ldflags
)
other.recursion_level = self.recursion_level + 1
other.find_pcfile(dep_name)
if other.errmsg:
self.errmsg = other.errmsg
break
self.cflags.extend(other.get_cflags_raw())
for level, libs in other.libs_deplist.items():
self.libs_deplist.setdefault(level, []).extend(libs)
def find_pcfile(self, libname):
self.recursion_level += 1
pcfile = f"{libname}.pc"
found_path = None
for path in self.search_path:
full_path = os.path.join(path, pcfile)
if os.path.exists(full_path):
found_path = full_path
break
if not found_path:
if not self.errmsg:
self.errmsg = f"Can't find {pcfile} in any of {self.search_path}\nuse the PKG_CONFIG_PATH environment variable."
self.recursion_level -= 1
return
self.parse_pcfile(found_path)
self.recursion_level -= 1
def get_cflags_raw(self):
return self.cflags
def get_cflags(self):
filtered = [f for f in self.cflags if f not in self.exclude_cflags]
return list(OrderedDict.fromkeys(filtered))
def get_ldflags(self):
ordered_libs = []
for level in sorted(self.libs_deplist.keys()):
lcopy = self.libs_deplist[level]
lcopy = [l for l in lcopy if l not in self.exclude_ldflags]
ordered_libs.extend(list(OrderedDict.fromkeys(lcopy)))
ordered_libs.reverse()
ordered_libs = list(OrderedDict.fromkeys(ordered_libs))
ordered_libs.reverse()
return ordered_libs
# -----------------------------------------------------------------------------
# Main CLI Logic
# -----------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Python Core-Only replacement for pkg-config")
parser.add_argument('libraries', nargs='*', help='Library names to query')
parser.add_argument('--libs', action='store_true', help='Output linker flags')
parser.add_argument('--libs-only-L', action='store_true', help='Output -L linker flags only')
parser.add_argument('--libs-only-l', action='store_true', help='Output -l linker flags only')
parser.add_argument('--cflags', action='store_true', help='Output compiler flags')
parser.add_argument('--cflags-only-I', action='store_true', help='Output -I compiler flags only')
parser.add_argument('--static', action='store_true', help='Use static dependencies')
parser.add_argument('--exists', action='store_true', help='Return 0 if the package exists')
parser.add_argument('--modversion', action='store_true', help='Print the version of the package')
parser.add_argument('--version', action='store_true', help='Print emulated pkg-config version')
parser.add_argument('--real-version', action='store_true', help='Print actual script version')
parser.add_argument('--with-path', action='append', default=[], help='Prepend to search paths')
parser.add_argument('--define-variable', action='append', default=[], help='Define a variable')
parser.add_argument('--variable', help='Return the value of a variable')
parser.add_argument('--print-variables', action='store_true', help='Print all defined variables')
parser.add_argument('--print-errors', action='store_true', help='Print errors to stderr')
parser.add_argument('--silence-errors', action='store_true', help='Turn off errors')
args = parser.parse_args()
if args.version:
print(f"0.{COMPAT_VERSION}")
sys.exit(0)
if args.real_version:
print(f"ppkg-config.py - cruftless pkg-config\nVersion: {VERSION}")
sys.exit(0)
if not args.libraries and not args.print_variables and not args.variable:
parser.print_help()
sys.exit(1)
uservars = {}
for var in args.define_variable:
if '=' in var:
k, v = var.split('=', 1)
uservars[k.strip()] = v.strip()
search_paths = args.with_path + DEFAULT_SEARCH_PATH
pkg = PkgConfig(
search_path=search_paths,
use_static=args.static,
uservars=uservars,
no_recurse=(args.exists or args.modversion)
)
for lib in args.libraries:
pkg.recursion_level = 0
pkg.find_pcfile(lib)
if pkg.errmsg:
if args.print_errors and not args.silence_errors:
sys.stderr.write(pkg.errmsg + "\n")
sys.exit(1)
if args.print_variables:
for k, v in pkg.defined_variables.items():
print(f"{k}={v}")
if args.variable:
val = pkg.filevars.get(args.variable, [])
print(" ".join(val) if isinstance(val, list) else val)
if args.modversion:
print(pkg.pkg_version)
sys.exit(0)
want_flags = args.libs or args.libs_only_L or args.libs_only_l or args.cflags or args.cflags_only_I
if not want_flags:
sys.exit(0)
print_flags = []
if args.cflags:
print_flags.extend(pkg.get_cflags())
if args.cflags_only_I:
print_flags.extend([f for f in pkg.get_cflags() if f.startswith('-I')])
if args.libs:
print_flags.extend(pkg.get_ldflags())
if args.libs_only_L:
print_flags.extend([f for f in pkg.get_ldflags() if f.startswith('-L') or f.startswith('-R')])
if args.libs_only_l:
print_flags.extend([f for f in pkg.get_ldflags() if f.startswith('-l')])
if print_flags:
print(" ".join(shlex.quote(f) for f in print_flags))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment