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.
300 lines
9.2 KiB
300 lines
9.2 KiB
#!/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())
|
|
|