blob: 078c3cb26681ca141ab6f21eb9481f18abb0dadf [file] [log] [blame]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Creates a archive manifest used for Fuchsia package generation."""
import argparse
import json
import os
import re
import subprocess
import sys
import tempfile
def MakePackagePath(file_path, roots):
"""Computes a path for |file_path| that is relative to one of the directory
paths in |roots|.
file_path: The file path to relativize.
roots: A list of directory paths which may serve as a relative root
for |file_path|.
Examples:
>>> MakePackagePath('/foo/bar.txt', ['/foo/'])
'bar.txt'
>>> MakePackagePath('/foo/dir/bar.txt', ['/foo/'])
'dir/bar.txt'
>>> MakePackagePath('/foo/out/Debug/bar.exe', ['/foo/', '/foo/out/Debug/'])
'bar.exe'
"""
# Prevents greedily matching against a shallow path when a deeper, better
# matching path exists.
roots.sort(key=len, reverse=True)
for next_root in roots:
if not next_root.endswith(os.sep):
next_root += os.sep
if file_path.startswith(next_root):
relative_path = file_path[len(next_root):]
return relative_path
return file_path
def _GetStrippedPath(bin_path):
"""Finds the stripped version of the binary |bin_path| in the build
output directory."""
return bin_path.replace('lib.unstripped/', 'lib/').replace(
'exe.unstripped/', '')
def _IsBinary(path):
"""Checks if the file at |path| is an ELF executable by inspecting its FourCC
header."""
with open(path, 'rb') as f:
file_tag = f.read(4)
return file_tag == '\x7fELF'
def _WriteBuildIdsTxt(binary_paths, ids_txt_path):
"""Writes an index text file that maps build IDs to the paths of unstripped
binaries."""
READELF_FILE_PREFIX = 'File: '
READELF_BUILD_ID_PREFIX = 'Build ID: '
# List of binaries whose build IDs are awaiting processing by readelf.
# Entries are removed as readelf's output is parsed.
unprocessed_binary_paths = {os.path.basename(p): p for p in binary_paths}
with open(ids_txt_path, 'w') as ids_file:
readelf_stdout = subprocess.check_output(
['readelf', '-n'] + map(_GetStrippedPath, binary_paths))
if len(binary_paths) == 1:
# Readelf won't report a binary's path if only one was provided to the
# tool.
binary_shortname = os.path.basename(binary_paths[0])
else:
binary_shortname = None
for line in readelf_stdout.split('\n'):
line = line.strip()
if line.startswith(READELF_FILE_PREFIX):
binary_shortname = os.path.basename(line[len(READELF_FILE_PREFIX):])
assert binary_shortname in unprocessed_binary_paths
elif line.startswith(READELF_BUILD_ID_PREFIX):
# Paths to the unstripped executables listed in "ids.txt" are specified
# as relative paths to that file.
unstripped_rel_path = os.path.relpath(
os.path.abspath(unprocessed_binary_paths[binary_shortname]),
os.path.dirname(os.path.abspath(ids_txt_path)))
build_id = line[len(READELF_BUILD_ID_PREFIX):]
ids_file.write(build_id + ' ' + unstripped_rel_path + '\n')
del unprocessed_binary_paths[binary_shortname]
# Did readelf forget anything? Make sure that all binaries are accounted for.
assert not unprocessed_binary_paths
def BuildManifest(args):
binaries = []
with open(args.manifest_path, 'w') as manifest, \
open(args.depfile_path, 'w') as depfile:
# Process the runtime deps file for file paths, recursively walking
# directories as needed.
# MakePackagePath() may relativize to either the source root or output
# directory.
# runtime_deps may contain duplicate paths, so use a set for
# de-duplication.
expanded_files = set()
for next_path in open(args.runtime_deps_file, 'r'):
next_path = next_path.strip()
if os.path.isdir(next_path):
for root, _, files in os.walk(next_path):
for current_file in files:
if current_file.startswith('.'):
continue
expanded_files.add(
os.path.join(root, current_file))
else:
expanded_files.add(next_path)
# Format and write out the manifest contents.
gen_dir = os.path.normpath(os.path.join(args.out_dir, "gen"))
app_found = False
excluded_files_set = set(args.exclude_file)
for current_file in expanded_files:
if _IsBinary(current_file):
binaries.append(current_file)
current_file = _GetStrippedPath(current_file)
in_package_path = MakePackagePath(current_file,
[gen_dir, args.root_dir, args.out_dir])
if in_package_path == args.app_filename:
app_found = True
if in_package_path in excluded_files_set:
excluded_files_set.remove(in_package_path)
continue
manifest.write('%s=%s\n' % (in_package_path, current_file))
if len(excluded_files_set) > 0:
raise Exception('Some files were excluded with --exclude-file, but '
'not found in the deps list: %s' %
', '.join(excluded_files_set));
if not app_found:
raise Exception('Could not locate executable inside runtime_deps.')
# Write meta/package manifest file.
with open(os.path.join(os.path.dirname(args.manifest_path), 'package'),
'w') as package_json:
json.dump({'version': '0', 'name': args.app_name}, package_json)
manifest.write('meta/package=%s\n' %
os.path.relpath(package_json.name, args.out_dir))
# Write component manifest file.
cmx_file_path = os.path.join(os.path.dirname(args.manifest_path),
args.app_name + '.cmx')
with open(cmx_file_path, 'w') as component_manifest_file:
component_manifest = {
'program': { 'binary': args.app_filename },
'sandbox': json.load(open(args.sandbox_policy_path, 'r')),
}
json.dump(component_manifest, component_manifest_file)
manifest.write('meta/%s=%s\n' %
(os.path.basename(component_manifest_file.name),
os.path.relpath(cmx_file_path, args.out_dir)))
depfile.write(
"%s: %s" % (os.path.relpath(args.manifest_path, args.out_dir),
" ".join([os.path.relpath(f, args.out_dir)
for f in expanded_files])))
_WriteBuildIdsTxt(binaries, args.build_ids_file)
return 0
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--root-dir', required=True, help='Build root directory')
parser.add_argument('--out-dir', required=True, help='Build output directory')
parser.add_argument('--app-name', required=True, help='Package name')
parser.add_argument('--app-filename', required=True,
help='Path to the main application binary relative to the output dir.')
parser.add_argument('--sandbox-policy-path', required=True,
help='Path to the sandbox policy file relative to the output dir.')
parser.add_argument('--runtime-deps-file', required=True,
help='File with the list of runtime dependencies.')
parser.add_argument('--depfile-path', required=True,
help='Path to write GN deps file.')
parser.add_argument('--exclude-file', action='append', default=[],
help='Package-relative file path to exclude from the package.')
parser.add_argument('--manifest-path', required=True,
help='Manifest output path.')
parser.add_argument('--build-ids-file', required=True,
help='Debug symbol index path.')
args = parser.parse_args()
return BuildManifest(args)
if __name__ == '__main__':
sys.exit(main())