summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--manifest_xml.py3
-rw-r--r--platform_utils.py43
-rw-r--r--platform_utils_win32.py63
-rw-r--r--project.py9
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:
32import gitc_utils 32import gitc_utils
33from git_config import GitConfig 33from git_config import GitConfig
34from git_refs import R_HEADS, HEAD 34from git_refs import R_HEADS, HEAD
35import platform_utils
35from project import RemoteSpec, Project, MetaProject 36from project import RemoteSpec, Project, MetaProject
36from error import ManifestParseError, ManifestInvalidRevisionError 37from 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
172def 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
190def _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
198def _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
16import errno
17
18from ctypes import WinDLL, get_last_error, FormatError, WinError
19from ctypes.wintypes import BOOL, LPCWSTR, DWORD
20
21kernel32 = WinDLL('kernel32', use_last_error=True)
22
23# Win32 error codes
24ERROR_SUCCESS = 0
25ERROR_PRIVILEGE_NOT_HELD = 1314
26
27# Win32 API entry points
28CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
29CreateSymbolicLinkW.restype = BOOL
30CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
31 LPCWSTR, # lpTargetFileName In
32 DWORD) # dwFlags In
33
34# Symbolic link creation flags
35SYMBOLIC_LINK_FLAG_FILE = 0x00
36SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
37
38
39def 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
44def 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
50def _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)
diff --git a/project.py b/project.py
index 269fd7e5..de5c7915 100644
--- a/project.py
+++ b/project.py
@@ -35,6 +35,7 @@ from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
35from error import GitError, HookError, UploadError, DownloadError 35from error import GitError, HookError, UploadError, DownloadError
36from error import ManifestInvalidRevisionError 36from error import ManifestInvalidRevisionError
37from error import NoManifestException 37from error import NoManifestException
38import platform_utils
38from trace import IsTrace, Trace 39from trace import IsTrace, Trace
39 40
40from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M 41from 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)