The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
9.2 KiB
301 lines
9.2 KiB
5 years ago
|
#!/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: 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
|
||
|
|
||
|
|
||
|
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_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
|
||
|
|
||
|
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())
|
||
|
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())
|