Skip to content

Commit 312b65c

Browse files
committed
Fix python packaging
1 parent 320590b commit 312b65c

File tree

2 files changed

+208
-20
lines changed

2 files changed

+208
-20
lines changed

cmake/yup_python.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function (yup_prepare_python_stdlib target_name python_tools_path output_variabl
2626

2727
cmake_parse_arguments (YUP_ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
2828

29-
set (default_ignored_library_patterns "lib2to3" "pydoc_data" "_xxtestfuzz*")
29+
set (default_ignored_library_patterns "lib2to3")
3030

3131
set (ignored_library_patterns ${default_ignored_library_patterns})
3232
list (APPEND ignored_library_patterns ${YUP_ARG_IGNORED_LIBRARY_PATTERNS})

python/tools/ArchivePythonStdlib.py

Lines changed: 207 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,68 @@ def file_hash(file):
1616
return h.hexdigest()
1717

1818

19-
def make_archive(file, directory, verbose=False):
19+
def should_ignore(path, patterns):
20+
"""Check if a path matches any of the ignore patterns using glob-style matching."""
21+
path_obj = Path(path)
22+
23+
for pattern in patterns:
24+
# Handle ** patterns for recursive matching
25+
if pattern.startswith("**/"):
26+
# Match pattern anywhere in the path
27+
pattern_without_prefix = pattern[3:]
28+
if any(part == pattern_without_prefix or Path(part).match(pattern_without_prefix) for part in path_obj.parts):
29+
return True
30+
# Also try matching the full pattern
31+
if path_obj.match(pattern):
32+
return True
33+
elif "**" in pattern:
34+
# Use pathlib match for patterns with **
35+
if path_obj.match(pattern):
36+
return True
37+
else:
38+
# For simple patterns, check if any path component matches
39+
if any(Path(part).match(pattern) for part in path_obj.parts):
40+
return True
41+
42+
return False
43+
44+
45+
def make_archive(file, directory, verbose=False, ignore_patterns=None):
2046
archived_files = []
21-
for dirname, _, files in os.walk(directory):
47+
for dirname, subdirs, files in os.walk(directory):
48+
rel_dirname = os.path.relpath(dirname, directory)
49+
50+
# Skip directories that match ignore patterns
51+
if ignore_patterns and rel_dirname != "." and should_ignore(rel_dirname, ignore_patterns):
52+
if verbose:
53+
print(f"Skipping directory: {rel_dirname}")
54+
# Clear subdirs to prevent os.walk from descending into this directory
55+
subdirs.clear()
56+
continue
57+
58+
# Filter out subdirectories that match ignore patterns to prevent descending
59+
if ignore_patterns:
60+
filtered_subdirs = []
61+
for subdir in subdirs:
62+
rel_subdir = os.path.relpath(os.path.join(dirname, subdir), directory)
63+
if should_ignore(rel_subdir, ignore_patterns):
64+
if verbose:
65+
print(f"Skipping subdirectory: {rel_subdir}")
66+
else:
67+
filtered_subdirs.append(subdir)
68+
subdirs[:] = filtered_subdirs
69+
2270
for filename in files:
2371
path = os.path.join(dirname, filename)
24-
archived_files.append((path, os.path.relpath(path, directory)))
72+
archive_path = os.path.relpath(path, directory)
73+
74+
# Skip files that match ignore patterns
75+
if ignore_patterns and should_ignore(archive_path, ignore_patterns):
76+
if verbose:
77+
print(f"Skipping file: {archive_path}")
78+
continue
79+
80+
archived_files.append((path, archive_path))
2581

2682
with zipfile.ZipFile(file, "w") as zf:
2783
for path, archive_path in sorted(archived_files):
@@ -59,49 +115,181 @@ def make_archive(file, directory, verbose=False):
59115
final_archive = args.output_folder / f"python{version_nodot}.zip"
60116
temp_archive = args.output_folder / f"temp{version_nodot}.zip"
61117

62-
base_python: Path = args.root_folder
118+
python_root: Path = args.root_folder
119+
python_lib_name = f"python{version}"
120+
121+
# Determine the structure based on what we're given
122+
# Case 1: We're given a path that ends with /lib (like Homebrew on macOS)
123+
if python_root.name == "lib":
124+
# Look for python{version} directly in this directory
125+
base_python_lib = python_root / python_lib_name
126+
if not base_python_lib.exists():
127+
# Try without the dot (e.g., python313)
128+
python_lib_name = f"python{version_nodot}"
129+
base_python_lib = python_root / python_lib_name
130+
131+
if base_python_lib.exists():
132+
lib_dir = python_root
133+
bin_dir = python_root.parent / "bin"
134+
include_dir = python_root.parent / "include"
135+
else:
136+
print(f"Error: Could not find Python library directory in {python_root}")
137+
print(f"Expected: {python_root}/python{version}/")
138+
exit(1)
139+
else:
140+
# Case 2: We're given a parent path, look for lib/python{version}
141+
lib_dir = python_root / "lib"
142+
bin_dir = python_root / "bin"
143+
include_dir = python_root / "include"
144+
145+
base_python_lib = lib_dir / python_lib_name
146+
if not base_python_lib.exists():
147+
# Try without the dot (e.g., python313)
148+
python_lib_name = f"python{version_nodot}"
149+
base_python_lib = lib_dir / python_lib_name
150+
151+
if not base_python_lib.exists():
152+
# Case 3: Maybe python_root itself is the python library directory
153+
if (python_root / "os.py").exists():
154+
base_python_lib = python_root
155+
lib_dir = python_root.parent
156+
bin_dir = lib_dir.parent / "bin" if lib_dir.name == "lib" else lib_dir / "bin"
157+
include_dir = lib_dir.parent / "include" if lib_dir.name == "lib" else lib_dir / "include"
158+
else:
159+
print(f"Error: Could not find Python library directory in {python_root}")
160+
print(f"Expected structure: {python_root}/lib/python{version}/")
161+
exit(1)
162+
163+
print(f"Python root: {python_root}")
164+
print(f"Python lib directory: {base_python_lib}")
165+
print(f"Bin directory: {bin_dir if bin_dir.exists() else 'not found'}")
166+
print(f"Include directory: {include_dir if include_dir.exists() else 'not found'}")
63167

64168
base_patterns = [
65169
"**/*.pyc",
66170
"**/__pycache__",
67171
"**/__phello__",
68-
"**/*config-3*",
69-
"**/*tcl*",
70-
"**/*tdbc*",
71-
"**/*tk*",
72-
"**/Tk*",
73172
"**/_tk*",
74173
"**/_test*",
75-
"**/libpython*",
76-
"**/pkgconfig",
174+
"**/_xxtestfuzz*",
175+
"**/*config-3*",
77176
"**/idlelib",
177+
"**/pkgconfig",
178+
"**/pydoc_data",
78179
"**/site-packages",
180+
"**/*tcl*",
181+
"**/*tdbc*",
182+
"**/temp_*.txt",
79183
"**/test",
80184
"**/turtledemo",
81-
"**/temp_*.txt",
185+
"**/*tk*",
186+
"**/Tk*",
82187
"**/.DS_Store",
83188
"**/EXTERNALLY-MANAGED",
84189
"**/LICENSE.txt",
85190
]
86191

192+
# Add custom patterns from cmake (like "test", "lib2to3", etc.)
87193
if args.exclude_patterns:
88-
custom_patterns = [x.strip() for x in args.exclude_patterns.replace('"', '').split(";")]
89-
base_patterns += custom_patterns
194+
custom_patterns = [x.strip() for x in args.exclude_patterns.replace('"', '').split(";") if x.strip()]
195+
# Convert simple patterns to ** patterns for recursive matching
196+
for pattern in custom_patterns:
197+
if not pattern.startswith("**/") and "**" not in pattern and "/" not in pattern:
198+
base_patterns.append(f"**/{pattern}")
199+
base_patterns.append(f"**/{pattern}/**")
200+
else:
201+
base_patterns.append(pattern)
90202

91-
ignored_files = shutil.ignore_patterns(*base_patterns)
203+
# Create a custom ignore function that uses glob matching for the lib directory
204+
def ignore_lib_files(directory, files):
205+
ignored = []
206+
for name in files:
207+
full_path = os.path.join(directory, name)
208+
rel_path = os.path.relpath(full_path, base_python_lib)
209+
if should_ignore(rel_path, base_patterns):
210+
ignored.append(name)
211+
if args.verbose:
212+
print(f"Ignoring during lib copy: {rel_path}")
213+
return ignored
214+
215+
# Custom ignore function for bin directory (keep python binaries only)
216+
def ignore_bin_files(directory, files):
217+
_ = directory # Required by shutil.copytree signature but not used
218+
ignored = []
219+
for name in files:
220+
# Keep only python* executables
221+
if not (name.startswith("python") and not name.endswith(".pyc")):
222+
ignored.append(name)
223+
if args.verbose:
224+
print(f"Ignoring during bin copy: {name}")
225+
return ignored
92226

93227
print(f"cleaning up {final_location}...")
94228
if final_location.exists():
95229
shutil.rmtree(final_location)
96230

97-
print(f"copying library from {base_python} to {final_location}...")
98-
shutil.copytree(base_python, final_location, ignore=ignored_files, dirs_exist_ok=True)
231+
# Create the lib/python{version} structure
232+
final_lib_location = final_location / "lib" / python_lib_name
233+
print(f"copying library from {base_python_lib} to {final_lib_location}...")
234+
shutil.copytree(base_python_lib, final_lib_location, ignore=ignore_lib_files, dirs_exist_ok=True)
235+
236+
# Copy bin directory if it exists
237+
if bin_dir.exists():
238+
final_bin_location = final_location / "bin"
239+
print(f"copying binaries from {bin_dir} to {final_bin_location}...")
240+
shutil.copytree(bin_dir, final_bin_location, ignore=ignore_bin_files, dirs_exist_ok=True)
241+
else:
242+
print(f"Warning: bin directory not found at {bin_dir}")
243+
244+
# Copy include directory if it exists
245+
if include_dir.exists():
246+
# Look for include/python{version} subdirectory
247+
include_python_subdir = include_dir / python_lib_name
248+
if include_python_subdir.exists():
249+
# Copy include/python{version} to final_location/include/python{version}
250+
final_include_location = final_location / "include" / python_lib_name
251+
print(f"copying headers from {include_python_subdir} to {final_include_location}...")
252+
shutil.copytree(include_python_subdir, final_include_location, dirs_exist_ok=True)
253+
else:
254+
# Copy entire include directory
255+
final_include_location = final_location / "include"
256+
print(f"copying headers from {include_dir} to {final_include_location}...")
257+
shutil.copytree(include_dir, final_include_location, dirs_exist_ok=True)
258+
else:
259+
print(f"Warning: include directory not found at {include_dir}")
260+
261+
# Update site_packages path to the new location
262+
site_packages = final_lib_location / "site-packages"
99263
os.makedirs(site_packages, exist_ok=True)
100264

265+
# Move libpython library files to lib/ directory
266+
# These are typically in config-3* directories but should be in lib/
267+
print(f"searching for libpython library files to move to lib/...")
268+
lib_extensions = [".dylib", ".dll", ".a", ".lib", ".so"]
269+
final_lib_root = final_location / "lib"
270+
271+
moved_files = []
272+
for root, dirs, files in os.walk(final_lib_location):
273+
for file in files:
274+
if file.startswith("libpython") and any(file.endswith(ext) for ext in lib_extensions):
275+
source_path = Path(root) / file
276+
# Move to lib/ directory (not lib/python{version}/)
277+
dest_path = final_lib_root / file
278+
279+
# Only move if it's not already in the lib/ directory
280+
if source_path.parent != final_lib_root:
281+
shutil.move(str(source_path), str(dest_path))
282+
moved_files.append(file)
283+
if args.verbose:
284+
print(f"Moved {file} from {source_path.parent} to {dest_path.parent}")
285+
286+
if moved_files:
287+
print(f"Moved {len(moved_files)} libpython library file(s) to lib/")
288+
101289
print(f"making archive {temp_archive} to {final_archive}...")
102290
if os.path.exists(final_archive):
103-
make_archive(temp_archive, final_location, verbose=args.verbose)
291+
make_archive(temp_archive, final_location, verbose=args.verbose, ignore_patterns=base_patterns)
104292
if file_hash(temp_archive) != file_hash(final_archive):
105293
shutil.copy(temp_archive, final_archive)
106294
else:
107-
make_archive(final_archive, final_location)
295+
make_archive(final_archive, final_location, ignore_patterns=base_patterns)

0 commit comments

Comments
 (0)