1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
#!/usr/bin/env python3
from fnmatch import fnmatch
import argparse
import json
import os
import sys
parser = argparse.ArgumentParser(usage='Run from an Xcode build phase to generate a tapi-installapi(1) compatible file list from XCBuild data.')
parser.add_argument(
'--from',
dest='source_headers',
required=True,
help="Path to a header VFS file (all-product-headers.yaml) OR a directory containing headers. Prefer using a VFS "
"file when the headers directory may contain stale headers from previous builds."
)
parser.add_argument(
'--install-dir',
required=True,
help="Directory that this target's headers will be installed to at the time tapi is run."
)
parser.add_argument(
'--relative-to',
help="Instead of assuming flattened header paths inside of install_dir, compute paths from the header's source "
"location relative to this one."
)
opts = parser.parse_args()
excluded_source_file_patterns = os.environ.get('EXCLUDED_SOURCE_FILE_NAMES', '').split()
headers = []
filelist = {'headers': [], 'version': '3'}
# Assume source_headers is an all-product-headers.yaml VFS file. In installhdrs
# builds, all-product-headers.yaml is not available, so walk the headers directory and prepare a file list
# using the headers that were just installed.
try:
vfs_fd = open(opts.source_headers)
except IsADirectoryError:
for dirpath, dirnames, filenames in os.walk(opts.source_headers):
headers.extend((name, os.path.join(dirpath, name)) for name in filenames)
# all-product-headers.yaml includes headers in the order they appear in the Xcode project, not in
# lexicographic order. But bmalloc and WTF's headers *are* declared in lexicographic order.
headers.sort()
else:
# By convention, all-product-headers.yaml is written in the JSON-compatible subset of YAML.
# Since PyYAML is not installed by default (rdar://107338320), first try parsing the file as
# JSON, and fall back to PyYAML as needed.
try:
product_headers = json.load(vfs_fd)
except json.JSONDecodeError:
import yaml
vfs_fd.seek(0)
product_headers = yaml.safe_load(vfs_fd)
for root in product_headers['roots']:
headers.extend((entry['name'], entry['external-contents']) for entry in root['contents'])
for name, source_path in headers:
if not name.endswith('.h') or any(fnmatch(source_path, f'*{pattern}') for pattern in excluded_source_file_patterns):
continue
if opts.relative_to:
relative_source_path = os.path.relpath(os.path.realpath(source_path), os.path.realpath(opts.relative_to))
assert not relative_source_path.startswith('../'), \
f"The header at {os.path.realpath(source_path)} is not relative to {os.path.realpath(opts.relative_to)}, bailing out to avoid unexpected paths in the TAPI filelist"
installed_path = os.path.normpath(os.path.join(opts.install_dir, relative_source_path))
else:
installed_path = os.path.join(opts.install_dir, name)
filelist['headers'].append({
'path': installed_path,
'type': 'private'
})
json.dump(filelist, sys.stdout, indent=2)
|