Bug 1290040 - Make find_program return short paths automatically when paths contain spaces on Windows. r?gps draft
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 29 Jul 2016 23:16:05 +0900
changeset 394632 2c032d09cef47f0cfe5a41bf20c1503dbf60d4bd
parent 394631 ff43d3e4c4fc8732b460ded61155c586e9e32573
child 394633 e6d4e3c4573ee715715b33701ebf4a04b043af0f
push id24612
push userbmo:mh+mozilla@glandium.org
push dateSat, 30 Jul 2016 04:51:39 +0000
reviewersgps
bugs1290040
milestone50.0a1
Bug 1290040 - Make find_program return short paths automatically when paths contain spaces on Windows. r?gps Also fake enough of ctypes to keep the configure unit tests passing after these changes.
build/moz.configure/util.configure
python/mozbuild/mozbuild/test/configure/common.py
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -56,36 +56,79 @@ def is_absolute_or_relative(path):
         return True
     return os.sep in path
 
 
 @imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
     return mozpath.normsep(path)
 
+
+@imports('ctypes')
+@imports(_from='ctypes', _import='wintypes')
+def get_GetShortPathNameW():
+    GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
+    GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR,
+                                  wintypes.DWORD]
+    GetShortPathNameW.restype = wintypes.DWORD
+    return GetShortPathNameW
+
+
+@template
+@imports('ctypes')
+@imports('platform')
+@imports(_from='mozbuild.shellutil', _import='quote')
+def normalize_path():
+    # Until the build system can properly handle programs that need quoting,
+    # transform those paths into their short version on Windows (e.g.
+    # c:\PROGRA~1...).
+    if platform.system() == 'Windows':
+        GetShortPathNameW = get_GetShortPathNameW()
+
+        def normalize_path(path):
+            path = normsep(path)
+            if quote(path) == path:
+                return path
+            size = 0
+            while True:
+                out = ctypes.create_unicode_buffer(size)
+                needed = GetShortPathNameW(path, out, size)
+                if size >= needed:
+                    return normsep(out.value)
+                size = needed
+
+    else:
+        def normalize_path(path):
+            return normsep(path)
+
+    return normalize_path
+
+normalize_path = normalize_path()
+
+
 # Locates the given program using which, or returns the given path if it
 # exists.
 # The `paths` parameter may be passed to search the given paths instead of
 # $PATH.
 @imports(_from='which', _import='which')
 @imports(_from='which', _import='WhichError')
 @imports('itertools')
 @imports(_from='os', _import='pathsep')
 def find_program(file, paths=None):
     try:
         if is_absolute_or_relative(file):
-            return normsep(which(os.path.basename(file),
-                                 [os.path.dirname(file)]))
+            return normalize_path(which(os.path.basename(file),
+                                        [os.path.dirname(file)]))
         if paths:
             if not isinstance(paths, (list, tuple)):
                 die("Paths provided to find_program must be a list of strings, "
                     "not %r", paths)
             paths = list(itertools.chain(
                 *(p.split(pathsep) for p in paths if p)))
-        return normsep(which(file, path=paths))
+        return normalize_path(which(file, path=paths))
     except WhichError:
         return None
 
 
 @imports('os')
 @imports('subprocess')
 @imports(_from='mozbuild.configure.util', _import='LineIO')
 @imports(_from='tempfile', _import='mkstemp')
--- a/python/mozbuild/mozbuild/test/configure/common.py
+++ b/python/mozbuild/mozbuild/test/configure/common.py
@@ -115,18 +115,54 @@ class ConfigureTestSandbox(ConfigureSand
                 PIPE=subprocess.PIPE,
                 STDOUT=subprocess.STDOUT,
                 Popen=self.Popen,
             )
 
         if what == 'os.environ':
             return self._environ
 
+        if what == 'ctypes.wintypes':
+            return ReadOnlyNamespace(
+                LPCWSTR=0,
+                LPWSTR=1,
+                DWORD=2,
+            )
+
+        if what == 'ctypes':
+            class CTypesFunc(object):
+                def __init__(self, func):
+                    self._func = func
+
+                def __call__(self, *args, **kwargs):
+                    return self._func(*args, **kwargs)
+
+
+            return ReadOnlyNamespace(
+                create_unicode_buffer=self.create_unicode_buffer,
+                windll=ReadOnlyNamespace(
+                    kernel32=ReadOnlyNamespace(
+                        GetShortPathNameW=CTypesFunc(self.GetShortPathNameW),
+                    )
+                ),
+            )
+
         return super(ConfigureTestSandbox, self)._get_one_import(what)
 
+    def create_unicode_buffer(self, *args, **kwargs):
+        class Buffer(object):
+            def __init__(self):
+                self.value = ''
+
+        return Buffer()
+
+    def GetShortPathNameW(self, path_in, path_out, length):
+        path_out.value = path_in
+        return length
+
     def which(self, command, path=None):
         for parent in (path or self._search_path):
             c = mozpath.abspath(mozpath.join(parent, command))
             for candidate in (c, ensure_exe_extension(c)):
                 if self.OS.path.exists(candidate):
                     return candidate
         raise WhichError()