Skip to content

Commit

Permalink
Add script for creating amalgamated distributions
Browse files Browse the repository at this point in the history
  • Loading branch information
athre0z committed Jan 9, 2022
1 parent 311fb0b commit bd39201
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ doc/html
cmake-build-debug
!tests/cases/*.out
*.pyc
/amalgamated-dist
183 changes: 183 additions & 0 deletions assets/amalgamate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python3

from pathlib import Path
from typing import List, Set
from glob import glob
from shutil import rmtree

import os
import re

ZYDIS_ROOT = Path(__file__).resolve().parent.parent
PUBLIC_INCLUDE_PATHS = [
ZYDIS_ROOT / 'include',
ZYDIS_ROOT / 'dependencies' / 'zycore' / 'include',
]
INTERNAL_INCLUDE_PATHS = [ZYDIS_ROOT / 'src']
INCLUDE_REGEXP = re.compile(r'^#\s*include\s*<((?:Zy|Generated).*)>\s*$')
OUTPUT_DIR = ZYDIS_ROOT / 'amalgamated-dist'
FILE_HEADER = ['// DO NOT EDIT. This file is auto-generated by `amalgamate.py`.', '']


# Python versions before 3.10 don't have the root_dir argument for glob, so we
# crudely emulate it here.
def glob_in_dir(
pattern: str,
root_dir: Path,
):
cwd = os.getcwd()
root_dir = root_dir.resolve()
os.chdir(root_dir)
try:
for path in glob(pattern, recursive=True):
yield Path(root_dir) / path
finally:
os.chdir(cwd)


def find_include_path(
include: str,
search_paths: List[Path],
) -> Path:
for search_path in search_paths:
path = search_path / include
if path.exists():
return path.absolute()
else:
raise FileNotFoundError(f'can\'t find header: {include}')


def merge_headers(
*,
header: str,
search_paths: List[Path],
covered_headers: Set[Path],
stack: List[str],
) -> List[str]:
# Locate and load header contents.
path = find_include_path(header, search_paths)
with path.open() as f:
lines = [x.strip() for x in f]

if path in covered_headers:
return []

print(f'Processing header "{header}"')
covered_headers.add(path)

# Print the header we emit next & the include stack (if non-root).
include_stack = []
if stack:
include_stack = [
'//',
'// Include stack:',
*(f'// - {x}' for x in stack)
]

filtered = [
f'',
f'//',
f'// Header: {header}',
*include_stack,
f'//',
f'',
]

# Copy over lines and recursively inline all headers.
for line in lines:
match = INCLUDE_REGEXP.match(line)
if not match:
filtered.append(line)
continue

# Recurse into includes.
filtered += merge_headers(
header=match.group(1),
search_paths=search_paths,
covered_headers=covered_headers,
stack=stack + [header],
)

return filtered


def merge_sources(*, source_dir: Path, covered_headers: Set[Path]):
output = [
'#include <Zydis.h>',
'',
]

for source_file in glob_in_dir('**/*.c', source_dir):
print(f'Processing source file "{source_file}"')

# Print some comments to show where the code is from.
output += [
f'',
f'//',
f'// Source file: {source_file}',
f'//',
f'',
]

# Read source file.
with (source_dir / source_file).open() as f:
lines = [x.rstrip() for x in f]

# Walk source file's lines.
for line in lines:
# Emit non-includes as-is.
match = INCLUDE_REGEXP.match(line)
if not match:
output.append(line)
continue
path = match.group(1)

if path in covered_headers:
continue

if 'Internal' not in path and 'Generated' not in path:
print(
f'WARN: Including header that looks like it is public '
f'and should thus already be covered by `Zydis.h` '
f'during processing of source files: {path}'
)

print(f'Processing internal header "{path}"')
output += merge_headers(
header=path,
search_paths=PUBLIC_INCLUDE_PATHS + INTERNAL_INCLUDE_PATHS,
covered_headers=covered_headers,
stack=[],
)

return output


def main():
if OUTPUT_DIR.exists():
print('Output directory exists. Deleting.')
rmtree(OUTPUT_DIR)

OUTPUT_DIR.mkdir()

covered_headers = set()
with open(OUTPUT_DIR / 'Zydis.h', 'w') as f:
f.write(os.linesep.join(FILE_HEADER + merge_headers(
header='Zydis/Zydis.h',
search_paths=PUBLIC_INCLUDE_PATHS,
covered_headers=covered_headers,
stack=[],
)))

print(covered_headers)

with open(OUTPUT_DIR / 'Zydis.c', 'w') as f:
f.write(os.linesep.join(FILE_HEADER + merge_sources(
source_dir=Path('src'),
covered_headers=covered_headers,
)))


if __name__ == '__main__':
main()

0 comments on commit bd39201

Please sign in to comment.