aboutsummaryrefslogtreecommitdiffstats
path: root/meson/tools/boost_names.py
diff options
context:
space:
mode:
Diffstat (limited to 'meson/tools/boost_names.py')
-rwxr-xr-xmeson/tools/boost_names.py300
1 files changed, 300 insertions, 0 deletions
diff --git a/meson/tools/boost_names.py b/meson/tools/boost_names.py
new file mode 100755
index 000000000..b716ccb8b
--- /dev/null
+++ b/meson/tools/boost_names.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python3
+
+# Copyright 2017 Niklas Claesson
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""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_excaped = re.sub(r"'", "\\'", mod.desc)
+ print(textwrap.indent(textwrap.dedent(f"""\
+ '{mod.key}': BoostModule(
+ name='{mod.name}',
+ key='{mod.key}',
+ desc='{desc_excaped}',
+ libs={[x.name for x in mod.libs]},
+ ),\
+ """), ' '))
+
+ print(textwrap.dedent(f'''\
+ }}
+
+ # #
+ #### ---- END GENERATED ---- ####\
+ '''))
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())