|  | #!/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()) |