Android, Python tools: bugfix in junction.py
junction.TempJunction now can create all parent
directories of the junction target if they don't
exist.
Change-Id: I3e9cf34e78a3eb1ef9415036b791843a3b37f7c1
PiperOrigin-RevId: 170176180
diff --git a/tools/android/junction.py b/tools/android/junction.py
index 7dc58b8..97fa8d2 100644
--- a/tools/android/junction.py
+++ b/tools/android/junction.py
@@ -63,7 +63,10 @@
...do something else...
"""
- def __init__(self, junction_target, testonly_mkdtemp=None):
+ def __init__(self,
+ junction_target,
+ testonly_mkdtemp=None,
+ testonly_maxpath=None):
"""Initialize this object.
Args:
@@ -72,14 +75,78 @@
testonly_mkdtemp: function(); for testing only; a custom function that
returns a temp directory path, you can use it to mock out
tempfile.mkdtemp
+ testonly_maxpath: int; for testing oly; maximum path length before the
+ path is a "long path" (typically MAX_PATH on Windows)
"""
- self._target = os.path.normpath(junction_target)
+ self._target = os.path.abspath(junction_target)
self._junction = None
self._mkdtemp = testonly_mkdtemp or tempfile.mkdtemp
+ self._max_path = testonly_maxpath or 248
+
+ @staticmethod
+ def _Mklink(name, target):
+ proc = subprocess.Popen(
+ "cmd.exe /C mklink /J \"%s\" \"\\\\?\\%s\"" % (name, target),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ exitcode = proc.wait()
+ if exitcode != 0:
+ stdout = proc.communicate()[0]
+ raise JunctionCreationError(name, target, stdout)
+
+ @staticmethod
+ def _TryMkdir(path):
+ try:
+ os.mkdir(path)
+ except OSError as e:
+ # Another process may have already created this directory.
+ if not os.path.isdir(path):
+ raise IOError("Could not create directory at '%s': %s" % (path, str(e)))
+
+ @staticmethod
+ def _MakeLinks(target, mkdtemp, max_path):
+ """Creates a temp directory and a junction in it, pointing to `target`.
+
+ Creates all parent directories of `target` if they don't exist.
+
+ Args:
+ target: string; path to the directory that is the junction's target
+ mkdtemp: function():string; creates a temp directory and returns its
+ absolute path
+ max_path: int; maximum path length before the path is a "long path"
+ (typically MAX_PATH on Windows)
+ Returns:
+ The full path to the junction.
+ Raises:
+ JunctionCreationError: if `mklink` fails to create a junction
+ """
+ segments = []
+ dirpath = target
+ while not os.path.isdir(dirpath):
+ dirpath, child = os.path.split(dirpath)
+ if child:
+ segments.append(child)
+ tmp = mkdtemp()
+ juncpath = os.path.join(tmp, "j")
+ for child in reversed(segments):
+ childpath = os.path.join(dirpath, child)
+ if len(childpath) >= max_path:
+ try:
+ TempJunction._Mklink(juncpath, dirpath)
+ TempJunction._TryMkdir(os.path.join(juncpath, child))
+ finally:
+ os.rmdir(juncpath)
+ else:
+ TempJunction._TryMkdir(childpath)
+ dirpath = childpath
+ TempJunction._Mklink(juncpath, target)
+ return juncpath
def __enter__(self):
"""Creates a temp directory and a junction in it, pointing to self._target.
+ Creates all parent directories of self._target if they don't exist.
+
This method is automatically called upon entering a `with` statement's body.
Returns:
@@ -87,18 +154,9 @@
Raises:
JunctionCreationError: if `mklink` fails to create a junction
"""
- result = os.path.normpath(os.path.join(self._mkdtemp(), "j"))
- proc = subprocess.Popen(
- "cmd.exe /C mklink /J \"%s\" \"\\\\?\\%s\"" %
- (result, os.path.normpath(self._target)),
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- exitcode = proc.wait()
- if exitcode != 0:
- stdout = proc.communicate()[0]
- raise JunctionCreationError(result, self._target, stdout)
- self._junction = result
- return result
+ self._junction = TempJunction._MakeLinks(self._target, self._mkdtemp,
+ self._max_path)
+ return self._junction
def __exit__(self, unused_type, unused_value, unused_traceback):
"""Deletes the junction and its parent directory.