| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2017 Niklas Claesson | |
| """This is two implementations for how to get module names from the boost | |
| sources. One relies on json metadata files in the sources, the other relies on | |
| the folder names. | |
| Run the tool in the boost directory and append the stdout to the misc.py: | |
| boost/$ path/to/meson/tools/boost_names.py >> path/to/meson/dependencies/misc.py | |
| """ | |
| import sys | |
| import json | |
| import re | |
| import textwrap | |
| import functools | |
| import typing as T | |
| from pathlib import Path | |
| lib_dir = Path('libs') | |
| jamroot = Path('Jamroot') | |
| not_modules = ['config', 'disjoint_sets', 'headers'] | |
| export_modules = False | |
| @functools.total_ordering | |
| class BoostLibrary(): | |
| def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): | |
| self.name = name | |
| self.shared = sorted(set(shared)) | |
| self.static = sorted(set(static)) | |
| self.single = sorted(set(single)) | |
| self.multi = sorted(set(multi)) | |
| def __lt__(self, other: object) -> bool: | |
| if isinstance(other, BoostLibrary): | |
| return self.name < other.name | |
| return NotImplemented | |
| def __eq__(self, other: object) -> bool: | |
| if isinstance(other, BoostLibrary): | |
| return self.name == other.name | |
| elif isinstance(other, str): | |
| return self.name == other | |
| return NotImplemented | |
| def __hash__(self) -> int: | |
| return hash(self.name) | |
| @functools.total_ordering | |
| class BoostModule(): | |
| def __init__(self, name: str, key: str, desc: str, libs: T.List[BoostLibrary]): | |
| self.name = name | |
| self.key = key | |
| self.desc = desc | |
| self.libs = libs | |
| def __lt__(self, other: object) -> bool: | |
| if isinstance(other, BoostModule): | |
| return self.key < other.key | |
| return NotImplemented | |
| def get_boost_version() -> T.Optional[str]: | |
| raw = jamroot.read_text(encoding='utf-8') | |
| m = re.search(r'BOOST_VERSION\s*:\s*([0-9\.]+)\s*;', raw) | |
| if m: | |
| return m.group(1) | |
| return None | |
| def get_libraries(jamfile: Path) -> T.List[BoostLibrary]: | |
| # Extract libraries from the boost Jamfiles. This includes: | |
| # - library name | |
| # - compiler flags | |
| libs: T.List[BoostLibrary] = [] | |
| raw = jamfile.read_text(encoding='utf-8') | |
| raw = re.sub(r'#.*\n', '\n', raw) # Remove comments | |
| raw = re.sub(r'\s+', ' ', raw) # Force single space | |
| raw = re.sub(r'}', ';', raw) # Cheat code blocks by converting } to ; | |
| cmds = raw.split(';') # Commands always terminate with a ; (I hope) | |
| cmds = [x.strip() for x in cmds] # Some cleanup | |
| project_usage_requirements: T.List[str] = [] | |
| # "Parse" the relevant sections | |
| for i in cmds: | |
| parts = i.split(' ') | |
| parts = [x for x in parts if x not in ['']] | |
| if not parts: | |
| continue | |
| # Parse project | |
| if parts[0] in ['project']: | |
| attributes: T.Dict[str, T.List[str]] = {} | |
| curr: T.Optional[str] = None | |
| for j in parts: | |
| if j == ':': | |
| curr = None | |
| elif curr is None: | |
| curr = j | |
| else: | |
| if curr not in attributes: | |
| attributes[curr] = [] | |
| attributes[curr] += [j] | |
| if 'usage-requirements' in attributes: | |
| project_usage_requirements = attributes['usage-requirements'] | |
| # Parse libraries | |
| elif parts[0] in ['lib', 'boost-lib']: | |
| assert len(parts) >= 2 | |
| # Get and check the library name | |
| lname = parts[1] | |
| if parts[0] == 'boost-lib': | |
| lname = f'boost_{lname}' | |
| if not lname.startswith('boost_'): | |
| continue | |
| # Count `:` to only select the 'usage-requirements' | |
| # See https://boostorg.github.io/build/manual/master/index.html#bbv2.main-target-rule-syntax | |
| colon_counter = 0 | |
| usage_requirements: T.List[str] = [] | |
| for j in parts: | |
| if j == ':': | |
| colon_counter += 1 | |
| elif colon_counter >= 4: | |
| usage_requirements += [j] | |
| # Get shared / static defines | |
| shared: T.List[str] = [] | |
| static: T.List[str] = [] | |
| single: T.List[str] = [] | |
| multi: T.List[str] = [] | |
| for j in usage_requirements + project_usage_requirements: | |
| m1 = re.match(r'<link>shared:<define>(.*)', j) | |
| m2 = re.match(r'<link>static:<define>(.*)', j) | |
| m3 = re.match(r'<threading>single:<define>(.*)', j) | |
| m4 = re.match(r'<threading>multi:<define>(.*)', j) | |
| if m1: | |
| shared += [f'-D{m1.group(1)}'] | |
| if m2: | |
| static += [f'-D{m2.group(1)}'] | |
| if m3: | |
| single +=[f'-D{m3.group(1)}'] | |
| if m4: | |
| multi += [f'-D{m4.group(1)}'] | |
| libs += [BoostLibrary(lname, shared, static, single, multi)] | |
| return libs | |
| def process_lib_dir(ldir: Path) -> T.List[BoostModule]: | |
| meta_file = ldir / 'meta' / 'libraries.json' | |
| bjam_file = ldir / 'build' / 'Jamfile.v2' | |
| if not meta_file.exists(): | |
| print(f'WARNING: Meta file {meta_file} does not exist') | |
| return [] | |
| # Extract libs | |
| libs: T.List[BoostLibrary] = [] | |
| if bjam_file.exists(): | |
| libs = get_libraries(bjam_file) | |
| # Extract metadata | |
| data = json.loads(meta_file.read_text(encoding='utf-8')) | |
| if not isinstance(data, list): | |
| data = [data] | |
| modules: T.List[BoostModule] = [] | |
| for i in data: | |
| modules += [BoostModule(i['name'], i['key'], i['description'], libs)] | |
| return modules | |
| def get_modules() -> T.List[BoostModule]: | |
| modules: T.List[BoostModule] = [] | |
| for i in lib_dir.iterdir(): | |
| if not i.is_dir() or i.name in not_modules: | |
| continue | |
| # numeric has sub libs | |
| subdirs = i / 'sublibs' | |
| metadir = i / 'meta' | |
| if subdirs.exists() and not metadir.exists(): | |
| for j in i.iterdir(): | |
| if not j.is_dir(): | |
| continue | |
| modules += process_lib_dir(j) | |
| else: | |
| modules += process_lib_dir(i) | |
| return modules | |
| def main() -> int: | |
| if not lib_dir.is_dir() or not jamroot.exists(): | |
| print("ERROR: script must be run in boost source directory") | |
| return 1 | |
| vers = get_boost_version() | |
| modules = get_modules() | |
| modules = sorted(modules) | |
| libraries = [x for y in modules for x in y.libs] | |
| libraries = sorted(set(libraries)) | |
| print(textwrap.dedent(f'''\ | |
| #### ---- BEGIN GENERATED ---- #### | |
| # # | |
| # Generated with tools/boost_names.py: | |
| # - boost version: {vers} | |
| # - modules found: {len(modules)} | |
| # - libraries found: {len(libraries)} | |
| # | |
| class BoostLibrary(): | |
| def __init__(self, name: str, shared: T.List[str], static: T.List[str], single: T.List[str], multi: T.List[str]): | |
| self.name = name | |
| self.shared = shared | |
| self.static = static | |
| self.single = single | |
| self.multi = multi | |
| class BoostModule(): | |
| def __init__(self, name: str, key: str, desc: str, libs: T.List[str]): | |
| self.name = name | |
| self.key = key | |
| self.desc = desc | |
| self.libs = libs | |
| # dict of all know libraries with additional compile options | |
| boost_libraries = {{\ | |
| ''')) | |
| for i in libraries: | |
| print(textwrap.indent(textwrap.dedent(f"""\ | |
| '{i.name}': BoostLibrary( | |
| name='{i.name}', | |
| shared={i.shared}, | |
| static={i.static}, | |
| single={i.single}, | |
| multi={i.multi}, | |
| ),\ | |
| """), ' ')) | |
| if export_modules: | |
| print(textwrap.dedent(f'''\ | |
| }} | |
| # dict of all modules with metadata | |
| boost_modules = {{\ | |
| ''')) | |
| for mod in modules: | |
| desc_escaped = re.sub(r"'", "\\'", mod.desc) | |
| print(textwrap.indent(textwrap.dedent(f"""\ | |
| '{mod.key}': BoostModule( | |
| name='{mod.name}', | |
| key='{mod.key}', | |
| desc='{desc_escaped}', | |
| libs={[x.name for x in mod.libs]}, | |
| ),\ | |
| """), ' ')) | |
| print(textwrap.dedent(f'''\ | |
| }} | |
| # # | |
| #### ---- END GENERATED ---- ####\ | |
| ''')) | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |