| # 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()) |