• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright 2014 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Copies files to a directory."""
7
8import filecmp
9import itertools
10import optparse
11import os
12import shutil
13import sys
14
15from util import build_utils
16
17
18def copy_tree(src,
19              dest,
20              follow_all_symlinks=False,
21              follow_outside_symlinks=False):
22    """copy src/* to dest/
23  I. If follow_outside_symlinks is true,
24     1. If src item is a symlink, and points to some item inside src,
25        then copy the symlink to dest.
26     2. If src item points to items outside src, then follow links to copy
27        the original file to dest.
28     3. Else copy src item to dest.
29  II. If follow_all_symlinks is true,
30     1. If src item is a symlink, then follow links to copy the original file
31        to dest.
32     2. Else copy src item to dest.
33  follow_outside_symlinks is true when follow_all_symlinks is true.
34  """
35    with os.scandir(src) as itr:
36        items = list(itr)
37    return _do_copy_tree(items,
38                         src,
39                         dest,
40                         follow_all_symlinks=follow_all_symlinks,
41                         follow_outside_symlinks=follow_outside_symlinks)
42
43
44def _do_copy_tree(items,
45                  src,
46                  dest,
47                  follow_all_symlinks=False,
48                  follow_outside_symlinks=False):
49    os.makedirs(dest, exist_ok=True)
50    for item in items:
51        srcname = os.path.join(src, item.name)
52        destname = os.path.join(dest, item.name)
53        is_symlink = item.is_symlink()
54        if is_symlink:
55            org_linkto = os.readlink(srcname)
56            linkto = org_linkto
57            if not os.path.isabs(org_linkto):
58                linkto = os.path.join(os.path.dirname(item), org_linkto)
59
60            if not os.path.exists(linkto):
61                os.symlink(org_linkto, destname)
62                shutil.copymode(srcname, destname, follow_symlinks=False)
63                continue
64
65            if follow_all_symlinks:
66                if item.is_dir():
67                    copy_tree(item,
68                              destname,
69                              follow_all_symlinks=follow_all_symlinks,
70                              follow_outside_symlinks=follow_outside_symlinks)
71                else:
72                    shutil.copy(item, destname)
73                    shutil.copymode(item, destname)
74
75            elif follow_outside_symlinks:
76                if os.path.abspath(src) in os.path.abspath(
77                        linkto) and not os.path.isabs(org_linkto):
78                    os.symlink(org_linkto, destname)
79                    shutil.copymode(srcname, destname, follow_symlinks=False)
80                else:
81                    if item.is_dir():
82                        copy_tree(
83                            item,
84                            destname,
85                            follow_all_symlinks=follow_all_symlinks,
86                            follow_outside_symlinks=follow_outside_symlinks)
87                    else:
88                        shutil.copy(item, destname)
89                        shutil.copymode(item, destname)
90            else:
91                os.symlink(org_linkto, destname)
92                shutil.copymode(srcname, destname, follow_symlinks=False)
93        elif item.is_dir():
94            copy_tree(item,
95                      destname,
96                      follow_all_symlinks=follow_all_symlinks,
97                      follow_outside_symlinks=follow_outside_symlinks)
98        else:
99            shutil.copy(item, destname)
100            shutil.copymode(item, destname)
101    shutil.copystat(src, dest)
102    return dest
103
104
105def copy_file(f,
106              dest,
107              deps,
108              follow_all_symlinks=False,
109              follow_outside_symlinks=False):
110    """Copy file or directory and update deps."""
111    if os.path.isdir(f):
112        copy_tree(f,
113                  os.path.join(dest, os.path.basename(f)),
114                  follow_all_symlinks=follow_all_symlinks,
115                  follow_outside_symlinks=follow_outside_symlinks)
116        deps.extend(build_utils.get_all_files(f))
117    else:
118        if os.path.isfile(os.path.join(dest, os.path.basename(f))):
119            dest = os.path.join(dest, os.path.basename(f))
120
121        deps.append(f)
122
123        if os.path.isfile(dest):
124            if filecmp.cmp(dest, f, shallow=False):
125                return
126            # The shutil.copy() below would fail if the file does not
127            # have write permissions. Deleting the file
128            # has similar costs to modifying the permissions.
129            os.unlink(dest)
130
131        shutil.copy(f, dest)
132
133
134def do_copy(options, deps):
135    """Copy files or directories given in options.files and update deps."""
136    files = list(
137        itertools.chain.from_iterable(
138            build_utils.parse_gn_list(f) for f in options.files))
139
140    for f in files:
141        if not options.ignore_stale:
142            if os.path.isdir(f) and not options.clear:
143                print('To avoid stale files you must use --clear when copying '
144                      'directories')
145                sys.exit(-1)
146        copy_file(f,
147                  options.dest,
148                  deps,
149                  follow_all_symlinks=options.follow_all_symlinks,
150                  follow_outside_symlinks=options.follow_outside_symlinks)
151
152
153def do_renaming(options, deps):
154    """Copy and rename files given in options.renaming_sources
155    and update deps.
156    """
157    src_files = list(
158        itertools.chain.from_iterable(
159            build_utils.parse_gn_list(f) for f in options.renaming_sources))
160
161    dest_files = list(
162        itertools.chain.from_iterable(
163            build_utils.parse_gn_list(f)
164            for f in options.renaming_destinations))
165
166    if (len(src_files) != len(dest_files)):
167        print('Renaming source and destination files not match.')
168        sys.exit(-1)
169
170    for src, dest in zip(src_files, dest_files):
171        if os.path.isdir(src):
172            print('renaming directory is not supported.')
173            sys.exit(-1)
174        else:
175            copy_file(src, os.path.join(options.dest, dest), deps)
176
177
178def main(args):
179    args = build_utils.expand_file_args(args)
180
181    parser = optparse.OptionParser()
182    build_utils.add_depfile_option(parser)
183
184    parser.add_option('--dest', help='Directory to copy files to.')
185    parser.add_option('--files',
186                      action='append',
187                      help='List of files to copy.')
188    parser.add_option(
189        '--clear',
190        action='store_true',
191        help='If set, the destination directory will be deleted '
192        'before copying files to it. This is highly recommended to '
193        'ensure that no stale files are left in the directory.')
194    parser.add_option('--ignore-stale',
195                      action='store_true',
196                      help='Do copy even there may be stale files. '
197                      'If set, overrule options.clear')
198    parser.add_option('--stamp', help='Path to touch on success.')
199    parser.add_option('--follow-all-symlinks',
200                      action='store_true',
201                      help='whether to follow symlinks')
202    parser.add_option(
203        '--follow-outside-symlinks',
204        action='store_true',
205        help='whether to follow symlinks which points to targets outside of'
206        'source directory')
207    parser.add_option('--renaming-sources',
208                      action='append',
209                      help='List of files need to be renamed while being '
210                      'copied to dest directory')
211    parser.add_option('--renaming-destinations',
212                      action='append',
213                      help='List of destination file name without path, the '
214                      'number of elements must match rename-sources.')
215
216    options, _ = parser.parse_args(args)
217    if options.follow_all_symlinks:
218        options.follow_outside_symlinks = True
219
220    if options.clear and not options.ignore_stale:
221        build_utils.delete_directory(options.dest)
222        build_utils.make_directory(options.dest)
223
224    deps = []
225
226    if options.files:
227        do_copy(options, deps)
228
229    if options.renaming_sources:
230        do_renaming(options, deps)
231
232    if options.depfile:
233        build_utils.write_depfile(options.depfile,
234                                  options.stamp,
235                                  deps,
236                                  add_pydeps=False)
237
238    if options.stamp:
239        build_utils.touch(options.stamp)
240
241
242if __name__ == '__main__':
243    sys.exit(main(sys.argv[1:]))
244