]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """distutils.file_util\r |
2 | \r | |
3 | Utility functions for operating on single files.\r | |
4 | """\r | |
5 | \r | |
6 | __revision__ = "$Id$"\r | |
7 | \r | |
8 | import os\r | |
9 | from distutils.errors import DistutilsFileError\r | |
10 | from distutils import log\r | |
11 | \r | |
12 | # for generating verbose output in 'copy_file()'\r | |
13 | _copy_action = {None: 'copying',\r | |
14 | 'hard': 'hard linking',\r | |
15 | 'sym': 'symbolically linking'}\r | |
16 | \r | |
17 | \r | |
18 | def _copy_file_contents(src, dst, buffer_size=16*1024):\r | |
19 | """Copy the file 'src' to 'dst'.\r | |
20 | \r | |
21 | Both must be filenames. Any error opening either file, reading from\r | |
22 | 'src', or writing to 'dst', raises DistutilsFileError. Data is\r | |
23 | read/written in chunks of 'buffer_size' bytes (default 16k). No attempt\r | |
24 | is made to handle anything apart from regular files.\r | |
25 | """\r | |
26 | # Stolen from shutil module in the standard library, but with\r | |
27 | # custom error-handling added.\r | |
28 | fsrc = None\r | |
29 | fdst = None\r | |
30 | try:\r | |
31 | try:\r | |
32 | fsrc = open(src, 'rb')\r | |
33 | except os.error, (errno, errstr):\r | |
34 | raise DistutilsFileError("could not open '%s': %s" % (src, errstr))\r | |
35 | \r | |
36 | if os.path.exists(dst):\r | |
37 | try:\r | |
38 | os.unlink(dst)\r | |
39 | except os.error, (errno, errstr):\r | |
40 | raise DistutilsFileError(\r | |
41 | "could not delete '%s': %s" % (dst, errstr))\r | |
42 | \r | |
43 | try:\r | |
44 | fdst = open(dst, 'wb')\r | |
45 | except os.error, (errno, errstr):\r | |
46 | raise DistutilsFileError(\r | |
47 | "could not create '%s': %s" % (dst, errstr))\r | |
48 | \r | |
49 | while 1:\r | |
50 | try:\r | |
51 | buf = fsrc.read(buffer_size)\r | |
52 | except os.error, (errno, errstr):\r | |
53 | raise DistutilsFileError(\r | |
54 | "could not read from '%s': %s" % (src, errstr))\r | |
55 | \r | |
56 | if not buf:\r | |
57 | break\r | |
58 | \r | |
59 | try:\r | |
60 | fdst.write(buf)\r | |
61 | except os.error, (errno, errstr):\r | |
62 | raise DistutilsFileError(\r | |
63 | "could not write to '%s': %s" % (dst, errstr))\r | |
64 | \r | |
65 | finally:\r | |
66 | if fdst:\r | |
67 | fdst.close()\r | |
68 | if fsrc:\r | |
69 | fsrc.close()\r | |
70 | \r | |
71 | def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,\r | |
72 | link=None, verbose=1, dry_run=0):\r | |
73 | """Copy a file 'src' to 'dst'.\r | |
74 | \r | |
75 | If 'dst' is a directory, then 'src' is copied there with the same name;\r | |
76 | otherwise, it must be a filename. (If the file exists, it will be\r | |
77 | ruthlessly clobbered.) If 'preserve_mode' is true (the default),\r | |
78 | the file's mode (type and permission bits, or whatever is analogous on\r | |
79 | the current platform) is copied. If 'preserve_times' is true (the\r | |
80 | default), the last-modified and last-access times are copied as well.\r | |
81 | If 'update' is true, 'src' will only be copied if 'dst' does not exist,\r | |
82 | or if 'dst' does exist but is older than 'src'.\r | |
83 | \r | |
84 | 'link' allows you to make hard links (os.link) or symbolic links\r | |
85 | (os.symlink) instead of copying: set it to "hard" or "sym"; if it is\r | |
86 | None (the default), files are copied. Don't set 'link' on systems that\r | |
87 | don't support it: 'copy_file()' doesn't check if hard or symbolic\r | |
88 | linking is available.\r | |
89 | \r | |
90 | Under Mac OS, uses the native file copy function in macostools; on\r | |
91 | other systems, uses '_copy_file_contents()' to copy file contents.\r | |
92 | \r | |
93 | Return a tuple (dest_name, copied): 'dest_name' is the actual name of\r | |
94 | the output file, and 'copied' is true if the file was copied (or would\r | |
95 | have been copied, if 'dry_run' true).\r | |
96 | """\r | |
97 | # XXX if the destination file already exists, we clobber it if\r | |
98 | # copying, but blow up if linking. Hmmm. And I don't know what\r | |
99 | # macostools.copyfile() does. Should definitely be consistent, and\r | |
100 | # should probably blow up if destination exists and we would be\r | |
101 | # changing it (ie. it's not already a hard/soft link to src OR\r | |
102 | # (not update) and (src newer than dst).\r | |
103 | \r | |
104 | from distutils.dep_util import newer\r | |
105 | from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE\r | |
106 | \r | |
107 | if not os.path.isfile(src):\r | |
108 | raise DistutilsFileError(\r | |
109 | "can't copy '%s': doesn't exist or not a regular file" % src)\r | |
110 | \r | |
111 | if os.path.isdir(dst):\r | |
112 | dir = dst\r | |
113 | dst = os.path.join(dst, os.path.basename(src))\r | |
114 | else:\r | |
115 | dir = os.path.dirname(dst)\r | |
116 | \r | |
117 | if update and not newer(src, dst):\r | |
118 | if verbose >= 1:\r | |
119 | log.debug("not copying %s (output up-to-date)", src)\r | |
120 | return dst, 0\r | |
121 | \r | |
122 | try:\r | |
123 | action = _copy_action[link]\r | |
124 | except KeyError:\r | |
125 | raise ValueError("invalid value '%s' for 'link' argument" % link)\r | |
126 | \r | |
127 | if verbose >= 1:\r | |
128 | if os.path.basename(dst) == os.path.basename(src):\r | |
129 | log.info("%s %s -> %s", action, src, dir)\r | |
130 | else:\r | |
131 | log.info("%s %s -> %s", action, src, dst)\r | |
132 | \r | |
133 | if dry_run:\r | |
134 | return (dst, 1)\r | |
135 | \r | |
136 | # If linking (hard or symbolic), use the appropriate system call\r | |
137 | # (Unix only, of course, but that's the caller's responsibility)\r | |
138 | if link == 'hard':\r | |
139 | if not (os.path.exists(dst) and os.path.samefile(src, dst)):\r | |
140 | os.link(src, dst)\r | |
141 | elif link == 'sym':\r | |
142 | if not (os.path.exists(dst) and os.path.samefile(src, dst)):\r | |
143 | os.symlink(src, dst)\r | |
144 | \r | |
145 | # Otherwise (non-Mac, not linking), copy the file contents and\r | |
146 | # (optionally) copy the times and mode.\r | |
147 | else:\r | |
148 | _copy_file_contents(src, dst)\r | |
149 | if preserve_mode or preserve_times:\r | |
150 | st = os.stat(src)\r | |
151 | \r | |
152 | # According to David Ascher <da@ski.org>, utime() should be done\r | |
153 | # before chmod() (at least under NT).\r | |
154 | if preserve_times:\r | |
155 | os.utime(dst, (st[ST_ATIME], st[ST_MTIME]))\r | |
156 | if preserve_mode:\r | |
157 | os.chmod(dst, S_IMODE(st[ST_MODE]))\r | |
158 | \r | |
159 | return (dst, 1)\r | |
160 | \r | |
161 | # XXX I suspect this is Unix-specific -- need porting help!\r | |
162 | def move_file (src, dst, verbose=1, dry_run=0):\r | |
163 | """Move a file 'src' to 'dst'.\r | |
164 | \r | |
165 | If 'dst' is a directory, the file will be moved into it with the same\r | |
166 | name; otherwise, 'src' is just renamed to 'dst'. Return the new\r | |
167 | full name of the file.\r | |
168 | \r | |
169 | Handles cross-device moves on Unix using 'copy_file()'. What about\r | |
170 | other systems???\r | |
171 | """\r | |
172 | from os.path import exists, isfile, isdir, basename, dirname\r | |
173 | import errno\r | |
174 | \r | |
175 | if verbose >= 1:\r | |
176 | log.info("moving %s -> %s", src, dst)\r | |
177 | \r | |
178 | if dry_run:\r | |
179 | return dst\r | |
180 | \r | |
181 | if not isfile(src):\r | |
182 | raise DistutilsFileError("can't move '%s': not a regular file" % src)\r | |
183 | \r | |
184 | if isdir(dst):\r | |
185 | dst = os.path.join(dst, basename(src))\r | |
186 | elif exists(dst):\r | |
187 | raise DistutilsFileError(\r | |
188 | "can't move '%s': destination '%s' already exists" %\r | |
189 | (src, dst))\r | |
190 | \r | |
191 | if not isdir(dirname(dst)):\r | |
192 | raise DistutilsFileError(\r | |
193 | "can't move '%s': destination '%s' not a valid path" % \\r | |
194 | (src, dst))\r | |
195 | \r | |
196 | copy_it = 0\r | |
197 | try:\r | |
198 | os.rename(src, dst)\r | |
199 | except os.error, (num, msg):\r | |
200 | if num == errno.EXDEV:\r | |
201 | copy_it = 1\r | |
202 | else:\r | |
203 | raise DistutilsFileError(\r | |
204 | "couldn't move '%s' to '%s': %s" % (src, dst, msg))\r | |
205 | \r | |
206 | if copy_it:\r | |
207 | copy_file(src, dst, verbose=verbose)\r | |
208 | try:\r | |
209 | os.unlink(src)\r | |
210 | except os.error, (num, msg):\r | |
211 | try:\r | |
212 | os.unlink(dst)\r | |
213 | except os.error:\r | |
214 | pass\r | |
215 | raise DistutilsFileError(\r | |
216 | ("couldn't move '%s' to '%s' by copy/delete: " +\r | |
217 | "delete '%s' failed: %s") %\r | |
218 | (src, dst, src, msg))\r | |
219 | return dst\r | |
220 | \r | |
221 | \r | |
222 | def write_file (filename, contents):\r | |
223 | """Create a file with the specified name and write 'contents' (a\r | |
224 | sequence of strings without line terminators) to it.\r | |
225 | """\r | |
226 | f = open(filename, "w")\r | |
227 | try:\r | |
228 | for line in contents:\r | |
229 | f.write(line + "\n")\r | |
230 | finally:\r | |
231 | f.close()\r |