| 
45 | 45 | # Assumption made in _path_join()  | 
46 | 46 | assert all(len(sep) == 1 for sep in path_separators)  | 
47 | 47 | path_sep = path_separators[0]  | 
 | 48 | +path_sep_tuple = tuple(path_separators)  | 
48 | 49 | path_separators = ''.join(path_separators)  | 
49 | 50 | _pathseps_with_colon = {f':{s}' for s in path_separators}  | 
50 | 51 | 
 
  | 
@@ -91,22 +92,49 @@ def _unpack_uint16(data):  | 
91 | 92 |     return int.from_bytes(data, 'little')  | 
92 | 93 | 
 
  | 
93 | 94 | 
 
  | 
94 |  | -def _path_join(*path_parts):  | 
95 |  | -    """Replacement for os.path.join()."""  | 
96 |  | -    return path_sep.join([part.rstrip(path_separators)  | 
97 |  | -                          for part in path_parts if part])  | 
 | 95 | +if _MS_WINDOWS:  | 
 | 96 | +    def _path_join(*path_parts):  | 
 | 97 | +        """Replacement for os.path.join()."""  | 
 | 98 | +        if not path_parts:  | 
 | 99 | +            return ""  | 
 | 100 | +        if len(path_parts) == 1:  | 
 | 101 | +            return path_parts[0]  | 
 | 102 | +        root = ""  | 
 | 103 | +        path = []  | 
 | 104 | +        for new_root, tail in map(_os._path_splitroot, path_parts):  | 
 | 105 | +            if new_root.startswith(path_sep_tuple) or new_root.endswith(path_sep_tuple):  | 
 | 106 | +                root = new_root.rstrip(path_separators) or root  | 
 | 107 | +                path = [path_sep + tail]  | 
 | 108 | +            elif new_root.endswith(':'):  | 
 | 109 | +                if root.casefold() != new_root.casefold():  | 
 | 110 | +                    # Drive relative paths have to be resolved by the OS, so we reset the  | 
 | 111 | +                    # tail but do not add a path_sep prefix.  | 
 | 112 | +                    root = new_root  | 
 | 113 | +                    path = [tail]  | 
 | 114 | +                else:  | 
 | 115 | +                    path.append(tail)  | 
 | 116 | +            else:  | 
 | 117 | +                root = new_root or root  | 
 | 118 | +                path.append(tail)  | 
 | 119 | +        path = [p.rstrip(path_separators) for p in path if p]  | 
 | 120 | +        if len(path) == 1 and not path[0]:  | 
 | 121 | +            # Avoid losing the root's trailing separator when joining with nothing  | 
 | 122 | +            return root + path_sep  | 
 | 123 | +        return root + path_sep.join(path)  | 
 | 124 | + | 
 | 125 | +else:  | 
 | 126 | +    def _path_join(*path_parts):  | 
 | 127 | +        """Replacement for os.path.join()."""  | 
 | 128 | +        return path_sep.join([part.rstrip(path_separators)  | 
 | 129 | +                              for part in path_parts if part])  | 
98 | 130 | 
 
  | 
99 | 131 | 
 
  | 
100 | 132 | def _path_split(path):  | 
101 | 133 |     """Replacement for os.path.split()."""  | 
102 |  | -    if len(path_separators) == 1:  | 
103 |  | -        front, _, tail = path.rpartition(path_sep)  | 
104 |  | -        return front, tail  | 
105 |  | -    for x in reversed(path):  | 
106 |  | -        if x in path_separators:  | 
107 |  | -            front, tail = path.rsplit(x, maxsplit=1)  | 
108 |  | -            return front, tail  | 
109 |  | -    return '', path  | 
 | 134 | +    i = max(path.rfind(p) for p in path_separators)  | 
 | 135 | +    if i < 0:  | 
 | 136 | +        return '', path  | 
 | 137 | +    return path[:i], path[i + 1:]  | 
110 | 138 | 
 
  | 
111 | 139 | 
 
  | 
112 | 140 | def _path_stat(path):  | 
@@ -140,13 +168,18 @@ def _path_isdir(path):  | 
140 | 168 |     return _path_is_mode_type(path, 0o040000)  | 
141 | 169 | 
 
  | 
142 | 170 | 
 
  | 
143 |  | -def _path_isabs(path):  | 
144 |  | -    """Replacement for os.path.isabs.  | 
 | 171 | +if _MS_WINDOWS:  | 
 | 172 | +    def _path_isabs(path):  | 
 | 173 | +        """Replacement for os.path.isabs."""  | 
 | 174 | +        if not path:  | 
 | 175 | +            return False  | 
 | 176 | +        root = _os._path_splitroot(path)[0].replace('/', '\\')  | 
 | 177 | +        return len(root) > 1 and (root.startswith('\\\\') or root.endswith('\\'))  | 
145 | 178 | 
 
  | 
146 |  | -    Considers a Windows drive-relative path (no drive, but starts with slash) to  | 
147 |  | -    still be "absolute".  | 
148 |  | -    """  | 
149 |  | -    return path.startswith(path_separators) or path[1:3] in _pathseps_with_colon  | 
 | 179 | +else:  | 
 | 180 | +    def _path_isabs(path):  | 
 | 181 | +        """Replacement for os.path.isabs."""  | 
 | 182 | +        return path.startswith(path_separators)  | 
150 | 183 | 
 
  | 
151 | 184 | 
 
  | 
152 | 185 | def _write_atomic(path, data, mode=0o666):  | 
@@ -707,6 +740,11 @@ def spec_from_file_location(name, location=None, *, loader=None,  | 
707 | 740 |                 pass  | 
708 | 741 |     else:  | 
709 | 742 |         location = _os.fspath(location)  | 
 | 743 | +        if not _path_isabs(location):  | 
 | 744 | +            try:  | 
 | 745 | +                location = _path_join(_os.getcwd(), location)  | 
 | 746 | +            except OSError:  | 
 | 747 | +                pass  | 
710 | 748 | 
 
  | 
711 | 749 |     # If the location is on the filesystem, but doesn't actually exist,  | 
712 | 750 |     # we could return None here, indicating that the location is not  | 
@@ -1451,6 +1489,8 @@ def __init__(self, path, *loader_details):  | 
1451 | 1489 |         self._loaders = loaders  | 
1452 | 1490 |         # Base (directory) path  | 
1453 | 1491 |         self.path = path or '.'  | 
 | 1492 | +        if not _path_isabs(self.path):  | 
 | 1493 | +            self.path = _path_join(_os.getcwd(), self.path)  | 
1454 | 1494 |         self._path_mtime = -1  | 
1455 | 1495 |         self._path_cache = set()  | 
1456 | 1496 |         self._relaxed_path_cache = set()  | 
@@ -1516,7 +1556,10 @@ def find_spec(self, fullname, target=None):  | 
1516 | 1556 |                 is_namespace = _path_isdir(base_path)  | 
1517 | 1557 |         # Check for a file w/ a proper suffix exists.  | 
1518 | 1558 |         for suffix, loader_class in self._loaders:  | 
1519 |  | -            full_path = _path_join(self.path, tail_module + suffix)  | 
 | 1559 | +            try:  | 
 | 1560 | +                full_path = _path_join(self.path, tail_module + suffix)  | 
 | 1561 | +            except ValueError:  | 
 | 1562 | +                return None  | 
1520 | 1563 |             _bootstrap._verbose_message('trying {}', full_path, verbosity=2)  | 
1521 | 1564 |             if cache_module + suffix in cache:  | 
1522 | 1565 |                 if _path_isfile(full_path):  | 
 | 
0 commit comments