• 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 
8 import filecmp
9 import itertools
10 import optparse
11 import os
12 import shutil
13 import sys
14 
15 from util import build_utils
16 
17 
18 def copy_tree(src: str,
19               dest: str,
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 
44 def _do_copy_tree(items: list,
45                   src: str,
46                   dest: str,
47                   follow_all_symlinks: bool=False,
48                   follow_outside_symlinks: bool=False) -> str:
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 
105 def copy_file(f: str,
106               dest: str,
107               deps: list,
108               follow_all_symlinks: bool=False,
109               follow_outside_symlinks: bool=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 
134 def do_copy(options, deps: list):
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 
153 def do_renaming(options, deps: list):
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 
178 def 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 
242 if __name__ == '__main__':
243     sys.exit(main(sys.argv[1:]))
244