|
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