Repository navigation
Expand file tree
/
Copy pathcmd_copy_lib.py
More file actions
138 lines (113 loc) · 4.11 KB
/
Copy pathcmd_copy_lib.py
File metadata and controls
138 lines (113 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env python
"""
Copy library files from CPython.
Usage:
# Single file
python scripts/update_lib copy-lib cpython/Lib/dataclasses.py
# Directory
python scripts/update_lib copy-lib cpython/Lib/json
"""
import argparse
import pathlib
import shutil
import sys
def _ignore_bytecode_caches(directory: str, names: list[str]) -> set[str]:
return {
name
for name in names
if name == "__pycache__" and (pathlib.Path(directory) / name).is_dir()
}
def _copy_single(
src_path: pathlib.Path,
lib_path: pathlib.Path,
verbose: bool = True,
) -> None:
"""Copy a single file or directory."""
# Remove existing file/directory
if lib_path.exists():
if lib_path.is_dir():
if verbose:
print(f"Removing directory: {lib_path}")
shutil.rmtree(lib_path)
else:
if verbose:
print(f"Removing file: {lib_path}")
lib_path.unlink()
# Copy
if src_path.is_dir():
if verbose:
print(f"Copying directory: {src_path} -> {lib_path}")
lib_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(src_path, lib_path, ignore=_ignore_bytecode_caches)
else:
if verbose:
print(f"Copying file: {src_path} -> {lib_path}")
lib_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, lib_path)
def _reject_python_tests(src_path: pathlib.Path, relative: pathlib.Path) -> None:
is_test_tree = len(relative.parts) >= 2 and relative.parts[1].casefold() == "test"
if is_test_tree and (src_path.is_dir() or src_path.suffix.casefold() == ".py"):
raise ValueError(
"Use migrate for Python tests; copy-lib would replace Lib/test"
)
def copy_lib(
src_path: pathlib.Path,
verbose: bool = True,
) -> None:
"""
Copy library file or directory from CPython.
Also copies additional files if defined in DEPENDENCIES table.
Args:
src_path: Source path (e.g., cpython/Lib/dataclasses.py or cpython/Lib/json)
verbose: Print progress messages
"""
from update_lib.deps import get_lib_paths
from update_lib.file_utils import parse_lib_path
# Extract module name and cpython prefix from path
path_str = str(src_path).replace("\\", "/")
if "/Lib/" not in path_str:
raise ValueError(f"Path must contain '/Lib/' (got: {src_path})")
if not src_path.exists():
raise FileNotFoundError(f"Source path does not exist: {src_path}")
relative = parse_lib_path(src_path)
_reject_python_tests(src_path, relative)
# Data files name an exact asset, not the containing top-level module.
# Expanding Lib/test/data.txt as "test" would replace the entire test tree.
if src_path.is_file() and src_path.suffix != ".py":
_copy_single(src_path, relative, verbose)
return
cpython_prefix, after_lib = path_str.split("/Lib/", 1)
# Get module name (first component, without .py)
name = after_lib.split("/")[0]
if name.endswith(".py"):
name = name[:-3]
# Get all paths to copy from DEPENDENCIES table
all_src_paths = get_lib_paths(name, cpython_prefix)
# Validate the entire expansion before removing any destination.
plan = [(src, parse_lib_path(src)) for src in all_src_paths if src.exists()]
for src, lib_path in plan:
_reject_python_tests(src, lib_path)
for src, lib_path in plan:
_copy_single(src, lib_path, verbose)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"path",
type=pathlib.Path,
help="Source path containing /Lib/ (e.g., cpython/Lib/dataclasses.py)",
)
args = parser.parse_args(argv)
try:
copy_lib(args.path)
return 0
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())