#!/usr/bin/env python3 # Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. import argparse import gn_helpers import os import re import shutil import sys def ParseArgs(args): args = args[1:] parser = argparse.ArgumentParser( description='A script to copy a file tree somewhere') parser.add_argument('--depfile', '-d', type=str, help='Path to a depfile to write when copying.') parser.add_argument( '--exclude_patterns', '-e', type=str, help='Patterns to exclude [passed to shutil.copytree]') parser.add_argument( '--from', '-f', dest="copy_from", type=str, help='Source directory') parser.add_argument( '--stamp', '-s', type=str, help='The path to a stamp file to output when finished.') parser.add_argument('--to', '-t', type=str, help='Destination directory') return parser.parse_args(args) def ValidateArgs(args): if not args.copy_from or not os.path.isdir(args.copy_from): print("--from argument must refer to a directory") return False if not args.to: print("--to is required") return False return True # Returns a list of the files under 'src' that were copied. def CopyTree(src, dst, ignore=None): copied_files = [] # Recursive helper method to collect errors but keep processing. def copy_tree(src, dst, ignore, errors): names = os.listdir(src) if ignore is not None: ignored_names = ignore(src, names) else: ignored_names = set() if not os.path.exists(dst): os.makedirs(dst) for name in names: if name in ignored_names: continue srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: if os.path.isdir(srcname): copy_tree(srcname, dstname, ignore, errors) else: copied_files.append(srcname) shutil.copy(srcname, dstname) except (IOError, os.error) as why: errors.append((srcname, dstname, str(why))) try: shutil.copystat(src, dst) except WindowsError: # Can't copy file access times on Windows. pass except OSError as why: errors.append((src, dst, str(why))) # Format errors from file copies. def format_error(error): if len(error) == 1: return "Error: {msg}".format(msg=str(error[0])) return "From: {src}\nTo: {dst}\n{msg}" \ .format(src=error[0], dst=error[1], msg=error[2]) errors = [] copy_tree(src, dst, ignore, errors) if errors: failures = "\n\n".join(format_error(error) for error in errors) parts = ("Some file copies failed:", "=" * 78, failures) msg = '\n'.join(parts) raise RuntimeError(msg) return copied_files def WriteDepfile(depfile, stamp, dep_list): os.makedirs(os.path.dirname(depfile), exist_ok=True) # Paths in the depfile must be relative to the root build output directory, # which is the cwd that ninja invokes the script from. cwd = os.getcwd() relstamp = os.path.relpath(stamp, cwd) reldep_list = [os.path.relpath(d, cwd) for d in dep_list] # Depfile paths must use an escape sequence for space characters. reldep_list = [path.replace(" ", r"\ ") for path in reldep_list] with open(depfile, 'w') as f: print("{0}: {1}".format(relstamp, " ".join(reldep_list)), file=f) def Main(argv): args = ParseArgs(argv) if not ValidateArgs(args): return -1 if args.exclude_patterns == None: copied_files = CopyTree(args.copy_from, args.to) else: patterns = args.exclude_patterns.split(',') copied_files = CopyTree(args.copy_from, args.to, ignore=shutil.ignore_patterns(*patterns)) if args.depfile and args.stamp: WriteDepfile(args.depfile, args.stamp, copied_files) if args.stamp: open(args.stamp, 'w').close() return 0 if __name__ == '__main__': sys.exit(Main(sys.argv))