"""Shared install inventory and configurable POSIX directory layout.""" from pathlib import Path KEYS = ('bindir', 'libdir', 'includedir', 'docdir', 'licensedir', 'examplesdir', 'desktopdir', 'pkgconfigdir') def arguments(parser): parser.add_argument('--prefix', default='/usr/local') parser.add_argument('--destdir', default='') for key in KEYS: parser.add_argument('--' + key) def layout(args): prefix = directory(args.prefix) defaults = dict(bindir=prefix / 'bin', libdir=prefix / 'lib', includedir=prefix / 'include', docdir=prefix / 'share/doc/xtc', licensedir=prefix / 'share/licenses/xtc', examplesdir=prefix / 'share/xtc/examples', desktopdir=prefix / 'share/applications', pkgconfigdir=prefix / 'lib/pkgconfig') return {key: directory(getattr(args, key) or value) for key, value in defaults.items()} def directory(value): text = str(value) path = Path(text) if not path.is_absolute() or '..' in path.parts or any(c in text for c in '\n\r\0"$\\'): raise ValueError('Install directories must be absolute without .., controls, quotes, $, or backslashes') return path def inventory(source, paths): entries = [('libdir', 'libxtc.a', None), ('pkgconfigdir', 'xtc.pc', None), ('bindir', 'xtc-check', None), ('bindir', 'xtc', None), ('bindir', 'xtc-juce', None), ('desktopdir', 'xtc.desktop', source / 'packaging/xtc.desktop'), ('licensedir', 'LICENSE', source / 'LICENSE'), ('licensedir', 'CLAP-LICENSE', source / 'third_party/clap/LICENSE'), ('licensedir', 'VST3-C-API-LICENSE', source / 'third_party/vst3/LICENSE.txt'), ('docdir', 'README.md', source / 'README.md'), ('docdir', 'BINARY_MANIFEST.json', None)] entries += [('includedir', 'xtc/' + p.name, p) for p in sorted((source / 'include/xtc').glob('*.h'))] entries += [('docdir', p.name, p) for p in sorted((source / 'docs').glob('*.md'))] entries += [('docdir', p.name, p) for p in sorted((source / 'docs').glob('*performance*.json'))] entries += [('docdir', 'decisions/' + p.name, p) for p in sorted((source / 'docs/decisions').glob('*.md'))] entries += [('examplesdir', p.name, p) for p in sorted((source / 'examples/projects').glob('*')) if p.is_file()] result = [(paths[key] / name, original) for key, name, original in entries] if len({path for path, _ in result}) != len(result): raise ValueError('Install layout has overlapping file destinations') return result def staged(path, destdir): if not destdir: return path root = directory(destdir).resolve() result = root / path.relative_to('/') if not result.parent.resolve().is_relative_to(root): raise ValueError(f'Install directory symlink escapes DESTDIR: {result}') return result