|
|
|
@ -24,164 +24,249 @@ boost/$ path/to/meson/tools/boost_names.py >> path/to/meson/dependencies/misc.py |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
import sys |
|
|
|
|
import os |
|
|
|
|
import collections |
|
|
|
|
import pprint |
|
|
|
|
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 = shared |
|
|
|
|
self.static = static |
|
|
|
|
self.single = single |
|
|
|
|
self.multi = multi |
|
|
|
|
|
|
|
|
|
def __lt__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: |
|
|
|
|
if isinstance(other, BoostLibrary): |
|
|
|
|
return self.name < other.name |
|
|
|
|
return NotImplemented |
|
|
|
|
|
|
|
|
|
def __eq__(self, other: T.Any) -> T.Union[bool, 'NotImplemented']: |
|
|
|
|
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: T.Any) -> T.Union[bool, 'NotImplemented']: |
|
|
|
|
if isinstance(other, BoostModule): |
|
|
|
|
return self.key < other.key |
|
|
|
|
return NotImplemented |
|
|
|
|
|
|
|
|
|
Module = collections.namedtuple('Module', ['dirname', 'name', 'libnames']) |
|
|
|
|
Module.__repr__ = lambda self: str((self.dirname, self.name, self.libnames)) # type: ignore |
|
|
|
|
|
|
|
|
|
LIBS = 'libs' |
|
|
|
|
|
|
|
|
|
manual_map = { |
|
|
|
|
'callable_traits': 'Call Traits', |
|
|
|
|
'crc': 'CRC', |
|
|
|
|
'dll': 'DLL', |
|
|
|
|
'gil': 'GIL', |
|
|
|
|
'graph_parallel': 'GraphParallel', |
|
|
|
|
'icl': 'ICL', |
|
|
|
|
'io': 'IO State Savers', |
|
|
|
|
'msm': 'Meta State Machine', |
|
|
|
|
'mpi': 'MPI', |
|
|
|
|
'mpl': 'MPL', |
|
|
|
|
'multi_array': 'Multi-Array', |
|
|
|
|
'multi_index': 'Multi-Index', |
|
|
|
|
'numeric': 'Numeric Conversion', |
|
|
|
|
'ptr_container': 'Pointer Container', |
|
|
|
|
'poly_collection': 'PolyCollection', |
|
|
|
|
'qvm': 'QVM', |
|
|
|
|
'throw_exception': 'ThrowException', |
|
|
|
|
'tti': 'TTI', |
|
|
|
|
'vmd': 'VMD', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
extra = [ |
|
|
|
|
Module('utility', 'Compressed Pair', []), |
|
|
|
|
Module('core', 'Enable If', []), |
|
|
|
|
Module('functional', 'Functional/Factory', []), |
|
|
|
|
Module('functional', 'Functional/Forward', []), |
|
|
|
|
Module('functional', 'Functional/Hash', []), |
|
|
|
|
Module('functional', 'Functional/Overloaded Function', []), |
|
|
|
|
Module('utility', 'Identity Type', []), |
|
|
|
|
Module('utility', 'In Place Factory, Typed In Place Factory', []), |
|
|
|
|
Module('numeric', 'Interval', []), |
|
|
|
|
Module('math', 'Math Common Factor', []), |
|
|
|
|
Module('math', 'Math Octonion', []), |
|
|
|
|
Module('math', 'Math Quaternion', []), |
|
|
|
|
Module('math', 'Math/Special Functions', []), |
|
|
|
|
Module('math', 'Math/Statistical Distributions', []), |
|
|
|
|
Module('bind', 'Member Function', []), |
|
|
|
|
Module('algorithm', 'Min-Max', []), |
|
|
|
|
Module('numeric', 'Odeint', []), |
|
|
|
|
Module('utility', 'Operators', []), |
|
|
|
|
Module('core', 'Ref', []), |
|
|
|
|
Module('utility', 'Result Of', []), |
|
|
|
|
Module('algorithm', 'String Algo', []), |
|
|
|
|
Module('core', 'Swap', []), |
|
|
|
|
Module('', 'Tribool', []), |
|
|
|
|
Module('numeric', 'uBLAS', []), |
|
|
|
|
Module('utility', 'Value Initialized', []), |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
# Cannot find the following modules in the documentation of boost |
|
|
|
|
not_modules = ['beast', 'logic', 'mp11', 'winapi'] |
|
|
|
|
|
|
|
|
|
def eprint(message): |
|
|
|
|
print(message, file=sys.stderr) |
|
|
|
|
|
|
|
|
|
def get_library_names(jamfile): |
|
|
|
|
libs = [] |
|
|
|
|
with open(jamfile) as jamfh: |
|
|
|
|
jam = jamfh.read() |
|
|
|
|
res = re.finditer(r'^lib[\s]+([A-Za-z0-9_]+)([^;]*);', jam, re.MULTILINE | re.DOTALL) |
|
|
|
|
for matches in res: |
|
|
|
|
if ':' in matches.group(2): |
|
|
|
|
libs.append(matches.group(1)) |
|
|
|
|
res = re.finditer(r'^boost-lib[\s]+([A-Za-z0-9_]+)([^;]*);', jam, re.MULTILINE | re.DOTALL) |
|
|
|
|
for matches in res: |
|
|
|
|
if ':' in matches.group(2): |
|
|
|
|
libs.append('boost_{}'.format(matches.group(1))) |
|
|
|
|
return libs |
|
|
|
|
|
|
|
|
|
def exists(modules, module): |
|
|
|
|
return len([x for x in modules if x.dirname == module.dirname]) != 0 |
|
|
|
|
def get_boost_version() -> T.Optional[str]: |
|
|
|
|
raw = jamroot.read_text() |
|
|
|
|
m = re.search(r'BOOST_VERSION\s*:\s*([0-9\.]+)\s*;', raw) |
|
|
|
|
if m: |
|
|
|
|
return m.group(1) |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def get_modules(init=extra): |
|
|
|
|
modules = init |
|
|
|
|
for directory in os.listdir(LIBS): |
|
|
|
|
if not os.path.isdir(os.path.join(LIBS, directory)): |
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
# "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 |
|
|
|
|
if directory in not_modules: |
|
|
|
|
|
|
|
|
|
# Parese libraries |
|
|
|
|
if 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 |
|
|
|
|
|
|
|
|
|
# Get shared / static defines |
|
|
|
|
shared: T.List[str] = [] |
|
|
|
|
static: T.List[str] = [] |
|
|
|
|
single: T.List[str] = [] |
|
|
|
|
multi: T.List[str] = [] |
|
|
|
|
for j in parts: |
|
|
|
|
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 += [m1.group(1)] |
|
|
|
|
if m2: |
|
|
|
|
static += [m2.group(1)] |
|
|
|
|
if m3: |
|
|
|
|
single += [m3.group(1)] |
|
|
|
|
if m4: |
|
|
|
|
multi += [m4.group(1)] |
|
|
|
|
|
|
|
|
|
shared = [f'-D{x}' for x in shared] |
|
|
|
|
static = [f'-D{x}' for x in static] |
|
|
|
|
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()) |
|
|
|
|
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 |
|
|
|
|
jamfile = os.path.join(LIBS, directory, 'build', 'Jamfile.v2') |
|
|
|
|
if os.path.isfile(jamfile): |
|
|
|
|
libs = get_library_names(jamfile) |
|
|
|
|
else: |
|
|
|
|
libs = [] |
|
|
|
|
if directory in manual_map.keys(): |
|
|
|
|
modname = manual_map[directory] |
|
|
|
|
|
|
|
|
|
# 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: |
|
|
|
|
modname = directory.replace('_', ' ').title() |
|
|
|
|
modules.append(Module(directory, modname, libs)) |
|
|
|
|
modules += process_lib_dir(i) |
|
|
|
|
|
|
|
|
|
return modules |
|
|
|
|
|
|
|
|
|
def get_modules_2(): |
|
|
|
|
modules = [] |
|
|
|
|
# The python module uses an older build system format and is not easily parseable. |
|
|
|
|
# We add the python module libraries manually. |
|
|
|
|
modules.append(Module('python', 'Python', ['boost_python', 'boost_python3', 'boost_numpy', 'boost_numpy3'])) |
|
|
|
|
for (root, _, files) in os.walk(LIBS): |
|
|
|
|
for f in files: |
|
|
|
|
if f == "libraries.json": |
|
|
|
|
projectdir = os.path.dirname(root) |
|
|
|
|
|
|
|
|
|
jamfile = os.path.join(projectdir, 'build', 'Jamfile.v2') |
|
|
|
|
if os.path.isfile(jamfile): |
|
|
|
|
libs = get_library_names(jamfile) |
|
|
|
|
else: |
|
|
|
|
libs = [] |
|
|
|
|
|
|
|
|
|
# Get metadata for module |
|
|
|
|
jsonfile = os.path.join(root, f) |
|
|
|
|
with open(jsonfile) as jsonfh: |
|
|
|
|
boost_modules = json.loads(jsonfh.read()) |
|
|
|
|
if(isinstance(boost_modules, dict)): |
|
|
|
|
boost_modules = [boost_modules] |
|
|
|
|
for boost_module in boost_modules: |
|
|
|
|
modules.append(Module(boost_module['key'], boost_module['name'], libs)) |
|
|
|
|
|
|
|
|
|
# Some subprojects do not have meta directory with json file. Find those |
|
|
|
|
jsonless_modules = [x for x in get_modules([]) if not exists(modules, x)] |
|
|
|
|
for module in jsonless_modules: |
|
|
|
|
eprint("WARNING: {} does not have meta/libraries.json. Will guess pretty name '{}'".format(module.dirname, module.name)) |
|
|
|
|
modules.extend(jsonless_modules) |
|
|
|
|
|
|
|
|
|
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'''\ |
|
|
|
|
}} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(args): |
|
|
|
|
if not os.path.isdir(LIBS): |
|
|
|
|
eprint("ERROR: script must be run in boost source directory") |
|
|
|
|
# dict of all modules with metadata |
|
|
|
|
boost_modules = {{\ |
|
|
|
|
''')) |
|
|
|
|
|
|
|
|
|
# It will pick jsonless algorithm if 1 is given as argument |
|
|
|
|
impl = 0 |
|
|
|
|
if len(args) > 1: |
|
|
|
|
if args[1] == '1': |
|
|
|
|
impl = 1 |
|
|
|
|
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]}, |
|
|
|
|
),\ |
|
|
|
|
"""), ' ')) |
|
|
|
|
|
|
|
|
|
if impl == 1: |
|
|
|
|
modules = get_modules() |
|
|
|
|
else: |
|
|
|
|
modules = get_modules_2() |
|
|
|
|
print(textwrap.dedent(f'''\ |
|
|
|
|
}} |
|
|
|
|
|
|
|
|
|
sorted_modules = sorted(modules, key=lambda module: module.name.lower()) |
|
|
|
|
sorted_modules = [x[2] for x in sorted_modules if x[2]] |
|
|
|
|
sorted_modules = sum(sorted_modules, []) |
|
|
|
|
sorted_modules = [x for x in sorted_modules if x.startswith('boost')] |
|
|
|
|
# # |
|
|
|
|
#### ---- END GENERATED ---- ####\ |
|
|
|
|
''')) |
|
|
|
|
|
|
|
|
|
pp = pprint.PrettyPrinter() |
|
|
|
|
pp.pprint(sorted_modules) |
|
|
|
|
return 0 |
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
main(sys.argv) |
|
|
|
|
sys.exit(main()) |
|
|
|
|