import argparse
import glob
import os
import re
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from check_minimal_symbols import undefined_rocksdb_symbols
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SYS_DIR = os.path.join(ROOT, "librocksdb-sys")
EXCLUDED = os.path.join(SYS_DIR, "zakura_excluded_sources.txt")
BUILD = [
"cargo", "build", "-p", "zakura-librocksdb-sys",
"--no-default-features", "--features", "lz4",
]
PROFILES = {"release": ["--release"], "debug": []}
def excluded_sources():
with open(EXCLUDED) as f:
return [l.strip() for l in f if l.strip() and not l.startswith("#")]
def build_archive(profile):
subprocess.run(BUILD + PROFILES[profile], cwd=ROOT, check=True)
archives = glob.glob(
os.path.join(ROOT, f"target/{profile}/build/zakura-librocksdb-sys-*/out/librocksdb.a")
)
return max(archives, key=os.path.getmtime)
def undefined_symbols():
return sorted({s for p in PROFILES for s in undefined_rocksdb_symbols(build_archive(p))})
def definition_patterns(symbol):
name = re.sub(r"^(vtable|typeinfo|typeinfo name) for ", "", symbol)
is_type = name != symbol
name = re.sub(r"\(.*$", "", name).replace("[abi:cxx11]", "")
parts = [p for p in name.split("::") if p and p != "rocksdb"]
if is_type:
needle = re.escape(parts[-1]) + r"::~?\w+"
elif len(parts) >= 2:
needle = re.escape(f"{parts[-2]}::{parts[-1]}")
else:
needle = r"\b" + re.escape(parts[-1])
return [r"(?m)^(?:[^\s#/][^;\n]*?)?" + needle + r"\s*(\(|=)"]
def defining_source(symbol, sources):
for source in sources:
with open(os.path.join(SYS_DIR, "rocksdb", source), errors="ignore") as f:
text = f.read()
if any(re.search(p, text) for p in definition_patterns(symbol)):
return source
return None
def restore(sources):
with open(EXCLUDED) as f:
lines = f.readlines()
with open(EXCLUDED, "w") as f:
f.writelines(l for l in lines if l.strip() not in sources)
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0])
parser.add_argument("--max-rounds", type=int, default=10)
args = parser.parse_args(argv)
for round_ in range(1, args.max_rounds + 1):
undefined = undefined_symbols()
print(f"round {round_}: {len(undefined)} undefined RocksDB symbols")
if not undefined:
return 0
excluded = excluded_sources()
to_restore = set()
unmatched = []
for symbol in undefined:
source = defining_source(symbol, excluded)
if source:
to_restore.add(source)
else:
unmatched.append(symbol)
for source in sorted(to_restore):
print(f" compiling again: {source}")
if unmatched:
print(" no skipped source defines:")
for symbol in unmatched:
print(f" {symbol}")
return 1
restore(to_restore)
print(f"still undefined after {args.max_rounds} rounds")
return 1
if __name__ == "__main__":
sys.exit(main())