diff options
-rw-r--r-- | manifest_xml.py | 3 | ||||
-rw-r--r-- | platform_utils.py | 43 | ||||
-rw-r--r-- | platform_utils_win32.py | 63 | ||||
-rw-r--r-- | project.py | 9 |
4 files changed, 114 insertions, 4 deletions
diff --git a/manifest_xml.py b/manifest_xml.py index 55d25a79..05651c6c 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -32,6 +32,7 @@ else: | |||
32 | import gitc_utils | 32 | import gitc_utils |
33 | from git_config import GitConfig | 33 | from git_config import GitConfig |
34 | from git_refs import R_HEADS, HEAD | 34 | from git_refs import R_HEADS, HEAD |
35 | import platform_utils | ||
35 | from project import RemoteSpec, Project, MetaProject | 36 | from project import RemoteSpec, Project, MetaProject |
36 | from error import ManifestParseError, ManifestInvalidRevisionError | 37 | from error import ManifestParseError, ManifestInvalidRevisionError |
37 | 38 | ||
@@ -166,7 +167,7 @@ class XmlManifest(object): | |||
166 | try: | 167 | try: |
167 | if os.path.lexists(self.manifestFile): | 168 | if os.path.lexists(self.manifestFile): |
168 | os.remove(self.manifestFile) | 169 | os.remove(self.manifestFile) |
169 | os.symlink(os.path.join('manifests', name), self.manifestFile) | 170 | platform_utils.symlink(os.path.join('manifests', name), self.manifestFile) |
170 | except OSError as e: | 171 | except OSError as e: |
171 | raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e))) | 172 | raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e))) |
172 | 173 | ||
diff --git a/platform_utils.py b/platform_utils.py index 1c719b1d..f4dfa0b1 100644 --- a/platform_utils.py +++ b/platform_utils.py | |||
@@ -167,3 +167,46 @@ class _FileDescriptorStreamsThreads(FileDescriptorStreams): | |||
167 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line)) | 167 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, line)) |
168 | self.fd.close() | 168 | self.fd.close() |
169 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None)) | 169 | self.queue.put(_FileDescriptorStreamsThreads.QueueItem(self, None)) |
170 | |||
171 | |||
172 | def symlink(source, link_name): | ||
173 | """Creates a symbolic link pointing to source named link_name. | ||
174 | Note: On Windows, source must exist on disk, as the implementation needs | ||
175 | to know whether to create a "File" or a "Directory" symbolic link. | ||
176 | """ | ||
177 | if isWindows(): | ||
178 | import platform_utils_win32 | ||
179 | source = _validate_winpath(source) | ||
180 | link_name = _validate_winpath(link_name) | ||
181 | target = os.path.join(os.path.dirname(link_name), source) | ||
182 | if os.path.isdir(target): | ||
183 | platform_utils_win32.create_dirsymlink(source, link_name) | ||
184 | else: | ||
185 | platform_utils_win32.create_filesymlink(source, link_name) | ||
186 | else: | ||
187 | return os.symlink(source, link_name) | ||
188 | |||
189 | |||
190 | def _validate_winpath(path): | ||
191 | path = os.path.normpath(path) | ||
192 | if _winpath_is_valid(path): | ||
193 | return path | ||
194 | raise ValueError("Path \"%s\" must be a relative path or an absolute " | ||
195 | "path starting with a drive letter".format(path)) | ||
196 | |||
197 | |||
198 | def _winpath_is_valid(path): | ||
199 | """Windows only: returns True if path is relative (e.g. ".\\foo") or is | ||
200 | absolute including a drive letter (e.g. "c:\\foo"). Returns False if path | ||
201 | is ambiguous (e.g. "x:foo" or "\\foo"). | ||
202 | """ | ||
203 | assert isWindows() | ||
204 | path = os.path.normpath(path) | ||
205 | drive, tail = os.path.splitdrive(path) | ||
206 | if tail: | ||
207 | if not drive: | ||
208 | return tail[0] != os.sep # "\\foo" is invalid | ||
209 | else: | ||
210 | return tail[0] == os.sep # "x:foo" is invalid | ||
211 | else: | ||
212 | return not drive # "x:" is invalid | ||
diff --git a/platform_utils_win32.py b/platform_utils_win32.py new file mode 100644 index 00000000..02fb013a --- /dev/null +++ b/platform_utils_win32.py | |||
@@ -0,0 +1,63 @@ | |||
1 | # | ||
2 | # Copyright (C) 2016 The Android Open Source Project | ||
3 | # | ||
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | # you may not use this file except in compliance with the License. | ||
6 | # You may obtain a copy of the License at | ||
7 | # | ||
8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | # | ||
10 | # Unless required by applicable law or agreed to in writing, software | ||
11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | # See the License for the specific language governing permissions and | ||
14 | # limitations under the License. | ||
15 | |||
16 | import errno | ||
17 | |||
18 | from ctypes import WinDLL, get_last_error, FormatError, WinError | ||
19 | from ctypes.wintypes import BOOL, LPCWSTR, DWORD | ||
20 | |||
21 | kernel32 = WinDLL('kernel32', use_last_error=True) | ||
22 | |||
23 | # Win32 error codes | ||
24 | ERROR_SUCCESS = 0 | ||
25 | ERROR_PRIVILEGE_NOT_HELD = 1314 | ||
26 | |||
27 | # Win32 API entry points | ||
28 | CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW | ||
29 | CreateSymbolicLinkW.restype = BOOL | ||
30 | CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In | ||
31 | LPCWSTR, # lpTargetFileName In | ||
32 | DWORD) # dwFlags In | ||
33 | |||
34 | # Symbolic link creation flags | ||
35 | SYMBOLIC_LINK_FLAG_FILE = 0x00 | ||
36 | SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01 | ||
37 | |||
38 | |||
39 | def create_filesymlink(source, link_name): | ||
40 | """Creates a Windows file symbolic link source pointing to link_name.""" | ||
41 | _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE) | ||
42 | |||
43 | |||
44 | def create_dirsymlink(source, link_name): | ||
45 | """Creates a Windows directory symbolic link source pointing to link_name. | ||
46 | """ | ||
47 | _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY) | ||
48 | |||
49 | |||
50 | def _create_symlink(source, link_name, dwFlags): | ||
51 | # Note: Win32 documentation for CreateSymbolicLink is incorrect. | ||
52 | # On success, the function returns "1". | ||
53 | # On error, the function returns some random value (e.g. 1280). | ||
54 | # The best bet seems to use "GetLastError" and check for error/success. | ||
55 | CreateSymbolicLinkW(link_name, source, dwFlags) | ||
56 | code = get_last_error() | ||
57 | if code != ERROR_SUCCESS: | ||
58 | error_desc = FormatError(code).strip() | ||
59 | if code == ERROR_PRIVILEGE_NOT_HELD: | ||
60 | raise OSError(errno.EPERM, error_desc, link_name) | ||
61 | error_desc = 'Error creating symbolic link %s: %s'.format( | ||
62 | link_name, error_desc) | ||
63 | raise WinError(code, error_desc) | ||
@@ -35,6 +35,7 @@ from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \ | |||
35 | from error import GitError, HookError, UploadError, DownloadError | 35 | from error import GitError, HookError, UploadError, DownloadError |
36 | from error import ManifestInvalidRevisionError | 36 | from error import ManifestInvalidRevisionError |
37 | from error import NoManifestException | 37 | from error import NoManifestException |
38 | import platform_utils | ||
38 | from trace import IsTrace, Trace | 39 | from trace import IsTrace, Trace |
39 | 40 | ||
40 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M | 41 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M |
@@ -277,7 +278,7 @@ class _LinkFile(object): | |||
277 | dest_dir = os.path.dirname(absDest) | 278 | dest_dir = os.path.dirname(absDest) |
278 | if not os.path.isdir(dest_dir): | 279 | if not os.path.isdir(dest_dir): |
279 | os.makedirs(dest_dir) | 280 | os.makedirs(dest_dir) |
280 | os.symlink(relSrc, absDest) | 281 | platform_utils.symlink(relSrc, absDest) |
281 | except IOError: | 282 | except IOError: |
282 | _error('Cannot link file %s to %s', relSrc, absDest) | 283 | _error('Cannot link file %s to %s', relSrc, absDest) |
283 | 284 | ||
@@ -2379,7 +2380,8 @@ class Project(object): | |||
2379 | self.relpath, name) | 2380 | self.relpath, name) |
2380 | continue | 2381 | continue |
2381 | try: | 2382 | try: |
2382 | os.symlink(os.path.relpath(stock_hook, os.path.dirname(dst)), dst) | 2383 | platform_utils.symlink( |
2384 | os.path.relpath(stock_hook, os.path.dirname(dst)), dst) | ||
2383 | except OSError as e: | 2385 | except OSError as e: |
2384 | if e.errno == errno.EPERM: | 2386 | if e.errno == errno.EPERM: |
2385 | raise GitError('filesystem must support symlinks') | 2387 | raise GitError('filesystem must support symlinks') |
@@ -2478,7 +2480,8 @@ class Project(object): | |||
2478 | os.makedirs(src) | 2480 | os.makedirs(src) |
2479 | 2481 | ||
2480 | if name in to_symlink: | 2482 | if name in to_symlink: |
2481 | os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst) | 2483 | platform_utils.symlink( |
2484 | os.path.relpath(src, os.path.dirname(dst)), dst) | ||
2482 | elif copy_all and not os.path.islink(dst): | 2485 | elif copy_all and not os.path.islink(dst): |
2483 | if os.path.isdir(src): | 2486 | if os.path.isdir(src): |
2484 | shutil.copytree(src, dst) | 2487 | shutil.copytree(src, dst) |