blob: 5053c012fc68c9fdc911ddd75b93ea425b5938d5 [file] [log] [blame]
# Copyright 2019 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Script to emit runtimes.json for Clang toolchain
This script works by taking a "prototype" spec as input, refining it, and
outputting the final runtimes.json spec. Refinement means adding possible
"debug" and "breakpad" entries for every runtime in the input spec.
"""
# [VPYTHON:BEGIN]
# python_version: "2.7"
# wheel <
# name: "fuchsia/python/wheels/attrs-py2_py3"
# version: "version:19.3.0"
# >
# wheel <
# name: "fuchsia/python/wheels/configparser-py2_py3"
# version: "version:4.0.2"
# >
# wheel <
# name: "fuchsia/python/wheels/contextlib2-py2_py3"
# version: "version:0.6.0"
# >
# wheel <
# name: "fuchsia/python/wheels/functools32-py2"
# version: "version:3.2.3.post2"
# >
# wheel <
# name: "fuchsia/python/wheels/importlib_metadata-py2_py3"
# version: "version:1.7.0"
# >
# wheel <
# name: "fuchsia/python/wheels/jsonschema-py2_py3"
# version: "version:3.2.0"
# >
# wheel <
# name: "fuchsia/python/wheels/pathlib2-py2_py3"
# version: "version:2.3.5"
# >
# wheel <
# name: "fuchsia/python/wheels/pyrsistent/${vpython_platform}"
# version: "version:0.16.0"
# >
# wheel <
# name: "fuchsia/python/wheels/scandir/${vpython_platform}"
# version: "version:1.10.0"
# >
# wheel <
# name: "fuchsia/python/wheels/six-py2_py3"
# version: "version:1.15.0"
# >
# wheel <
# name: "fuchsia/python/wheels/zipp-py2_py3"
# version: "version:1.2.0"
# >
# [VPYTHON:END]
from __future__ import print_function
import argparse
import errno
import glob
import json
import jsonschema
import os
import re
import shutil
import subprocess
import sys
SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"required": ["target", "runtime"],
"properties": {
"target": {"type": "array", "items": {"type": "string"}},
"runtime": {
"type": "array",
"items": {
"type": "object",
"properties": {
"dist": {"type": "string"},
"name": {"type": "string"},
# populated in the output:
# - soname (if present)
# - debug (optional)
# - breakpad (optional)
},
"required": ["dist"],
},
},
"cflags": {"type": "array", "items": {"type": "string"}},
"ldflags": {"type": "array", "items": {"type": "string"}},
"rustflags": {"type": "array", "items": {"type": "string"}},
},
},
}
def read_soname_and_build_id(readelf, filename):
p = subprocess.Popen(
[readelf, "-Wnd", filename], stdout=subprocess.PIPE, env={"LC_ALL": "C"}
)
stdout, _ = p.communicate()
if p.returncode != 0:
raise Exception("failed to read notes")
match = re.search(r"Library soname: \[([a-zA-Z0-9.+_-]+)\]", stdout)
soname = match.group(1) if match else None
match = re.search(r"Build ID: ([a-zA-Z0-9_-]+)", stdout)
if not match:
raise Exception("build ID missing")
build_id = match.group(1)
return soname, build_id
def strip_and_populate_build_id(build_id_path, runtime, debug_file, objcopy):
# If objcopy was supplied, assume the library still needs to be
# stripped and build-id entries need to be created.
if os.path.exists(build_id_path):
print("%s exists, linking %s" % (build_id_path, runtime), file=sys.stderr)
os.remove(runtime)
try:
os.link(build_id_path, runtime)
except AttributeError:
shutil.copyfile(build_id_path, runtime)
return
try:
# TODO(tmandry): try/except can be replaced with exist_ok=True once we
# switch to Python 3.
os.makedirs(os.path.dirname(build_id_path))
except OSError as e:
# Don't error if two build ids share the same 2-digit prefix.
if e.errno != errno.EEXIST:
raise
subprocess.check_call([objcopy, "--strip-all", runtime, build_id_path])
os.rename(runtime, debug_file)
try:
os.link(build_id_path, runtime)
except AttributeError:
shutil.copyfile(build_id_path, runtime)
def finalize_entry(dirname, build_id_repo, readelf, dump_syms, objcopy, entry):
pattern = entry["dist"]
filenames = glob.glob(os.path.join(dirname, pattern))
if len(filenames) != 1:
raise ValueError(
"Expected the pattern '%s' to match exactly one file, but got these matches: %r"
% (pattern, filenames)
)
filename = os.path.relpath(filenames[0], dirname)
entry["dist"] = filename
def full_path(filename):
return os.path.realpath(os.path.join(dirname, filename))
soname, build_id = read_soname_and_build_id(readelf, full_path(filename))
if soname:
entry["soname"] = soname
build_id_path = build_id_repo + "/%s/%s" % (build_id[0:2], build_id[2:])
debug_file = build_id_path + ".debug"
entry["debug"] = build_id_path + ".debug"
if objcopy:
strip_and_populate_build_id(
full_path(build_id_path),
full_path(filename),
full_path(debug_file),
objcopy,
)
if dump_syms:
breakpad_file = build_id_path + ".sym"
with open(full_path(breakpad_file), "w") as f:
soname = soname or os.path.basename(filename)
subprocess.check_call(
[dump_syms, "-r", "-n", soname, "-o", "Fuchsia", full_path(debug_file)],
stdout=f,
)
entry["breakpad"] = breakpad_file
return entry
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--dir", default=os.getcwd(), help="runtime.json directory")
parser.add_argument(
"--build-id-repo",
default=".build-id",
help="build-id repo path, relative to runtime.json directory",
)
parser.add_argument("--readelf", required=True, help="path to readelf utility")
parser.add_argument(
"--objcopy",
help="path to llvm-objcopy, used for stripping and populating .build-id",
)
parser.add_argument("--dump_syms", help="path to Breakpad dump_syms utility")
args = parser.parse_args()
runtimes = json.load(sys.stdin)
jsonschema.validate(runtimes, schema=SCHEMA)
for configuration in runtimes:
for runtime in configuration["runtime"]:
runtime = finalize_entry(
args.dir,
args.build_id_repo,
args.readelf,
args.dump_syms,
args.objcopy,
runtime,
)
json.dump(runtimes, sys.stdout, indent=2, sort_keys=True)
return 0
if __name__ == "__main__":
sys.exit(main())