| #!/usr/bin/env python3.8 |
| # 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. |
| """ |
| A script used to populate a sysroot include directory exactly as specified by an input .json file. |
| Note that the script will ensure that any stale items in the output directory that are not listed |
| in the input file will be removed. Without this, when a header is removed from the sysroot |
| description, its old copy would linger on in the build output directory, which can lead to |
| silently broken builds that are hard to detect. |
| """ |
| |
| # IMPORTANT: This relies on the JSON schema definition described in BUILD.gn in |
| # the same directory. Keep this script and the code in that file in sync at all |
| # times for correctness! |
| |
| from __future__ import print_function |
| |
| import argparse |
| import filecmp |
| import json |
| import os |
| import shutil |
| import sys |
| |
| |
| def _get_directory_contents(path): |
| """Return the files and directories under |path| as a list of paths.""" |
| result = [] |
| for root, dirs, files in os.walk(path): |
| assert root.startswith(path), 'root %s prefix %s' % (root, path) |
| subdir = root[len(path):] |
| if subdir: |
| assert subdir[0] == os.sep, 'Invalid subdir %s' % subdir |
| subdir = subdir[1:] |
| for file in files: |
| result.append(os.path.join(subdir, file)) |
| for dir in dirs: |
| result.append(os.path.join(subdir, dir) + os.sep) |
| return set(result) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| |
| parser.add_argument( |
| '--src-dir', required=True, help='Root source directory.') |
| |
| parser.add_argument( |
| '--build-dir', |
| help= |
| 'Build output directory of generated headers. Default to current one.') |
| |
| parser.add_argument( |
| '--sysroot-json', |
| required=True, |
| help='Input JSON file describing the content of the sysroot.') |
| |
| parser.add_argument( |
| '--sysroot-dir', |
| required=True, |
| help='Output directory to be populated by this script.') |
| |
| parser.add_argument( |
| '--debug', |
| action='store_true', |
| help='Enable verbose mode for debugging this script.') |
| |
| parser.add_argument( |
| '--dep-file', |
| help='Optional dependency file to be generated by this script.') |
| |
| args = parser.parse_args() |
| |
| if args.build_dir: |
| root_build_dir = os.path.abspath(args.build_dir) |
| else: |
| root_build_dir = os.getcwd() |
| |
| sysroot_dir = os.path.abspath(args.sysroot_dir) |
| sysroot_include_dir = os.path.join(sysroot_dir, 'include') |
| |
| sysroot_include_old_files = _get_directory_contents(sysroot_include_dir) |
| if args.debug: |
| print('CURRENT CONTENTS FOR %s:' % sysroot_include_dir) |
| print('\n'.join(sorted(sysroot_include_old_files))) |
| |
| with open(args.sysroot_json) as f: |
| sysroot_json = json.load(f) |
| |
| root_src_dir = os.path.abspath(args.src_dir) |
| |
| # This maps destination file paths (relative to sysroot_include_dir) to |
| # their source file location as in appears in the input JSON file. |
| # Note that source file paths that begin with // are relative to the |
| # root source directory, while others are relative to the build output |
| # directory instead. |
| sysroot_copy_map = {} |
| |
| sysroot_include_new_files = set() |
| for entry in sysroot_json: |
| if 'sdk' not in entry: |
| continue |
| sdk = entry['sdk'] |
| if 'include_dir' not in sdk: |
| continue |
| |
| # This is a list of headers to copy. |
| for header in sdk['headers']: |
| sysroot_include_new_files.add(header) |
| sysroot_include_new_files.add(os.path.dirname(header) + os.sep) |
| sysroot_copy_map[header] = os.path.join(sdk['include_dir'], header) |
| |
| if args.debug: |
| print('SYSROOT/include INPUT CONTENTS:') |
| print('\n'.join(sorted(sysroot_include_new_files))) |
| |
| # For any input file, compare with the existing version if any to avoid |
| # touching the sysroot file if they are the same. |
| dep_entries = [] |
| for entry in sorted(sysroot_include_new_files): |
| dst_path = os.path.join(sysroot_include_dir, entry) |
| |
| if entry.endswith(os.sep): |
| # This is a directory entry. |
| if not os.path.exists(dst_path): |
| if args.debug: |
| print('MKDIR ' + dst_path) |
| os.makedirs(dst_path) |
| else: |
| # This is a file entry. |
| src_path = sysroot_copy_map[entry] |
| if src_path.startswith('//'): |
| src_path = os.path.join(root_src_dir, src_path[2:]) |
| else: |
| src_path = os.path.join(root_build_dir, src_path) |
| |
| dep_entries += [ |
| '%s: %s' % ( |
| os.path.relpath(dst_path, root_build_dir), |
| os.path.relpath(src_path, root_build_dir)) |
| ] |
| if os.path.exists(dst_path) and filecmp.cmp(dst_path, src_path): |
| if args.debug: |
| print('SKIP ' + dst_path) |
| continue |
| |
| if args.debug: |
| print('COPY %s <-- %s' % (dst_path, src_path)) |
| shutil.copyfile(src_path, dst_path) |
| |
| # Remove any extra files or directories that are no longer needed. |
| # Use reverse sort to ensure that files are removed before the |
| # directories that contain them. |
| for extra in sorted(sysroot_include_old_files - sysroot_include_new_files, |
| reverse=True): |
| extra_path = os.path.join(sysroot_include_dir, extra) |
| if extra.endswith(os.sep): |
| if args.debug: |
| print('REMOVING DIR ' + extra_path) |
| os.rmdir(extra_path) |
| else: |
| if args.debug: |
| print('REMOVING FILE ' + extra_path) |
| os.unlink(extra_path) |
| |
| if args.dep_file: |
| with open(args.dep_file, 'wt') as f: |
| f.write('\n'.join(dep_entries)) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |