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