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