zarchive-py 0.0.1b3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zarchive_py-0.0.1b3/MANIFEST.in +8 -0
- zarchive_py-0.0.1b3/PKG-INFO +24 -0
- zarchive_py-0.0.1b3/README.md +241 -0
- zarchive_py-0.0.1b3/_bundle_native.py +425 -0
- zarchive_py-0.0.1b3/_plat.py +167 -0
- zarchive_py-0.0.1b3/pyproject.toml +18 -0
- zarchive_py-0.0.1b3/setup.cfg +4 -0
- zarchive_py-0.0.1b3/setup.py +80 -0
- zarchive_py-0.0.1b3/tests/__init__.py +1 -0
- zarchive_py-0.0.1b3/tests/test_zarchive.py +999 -0
- zarchive_py-0.0.1b3/zarchive/__init__.py +107 -0
- zarchive_py-0.0.1b3/zarchive/_bin/.gitkeep +0 -0
- zarchive_py-0.0.1b3/zarchive/_native.py +660 -0
- zarchive_py-0.0.1b3/zarchive/archive.py +458 -0
- zarchive_py-0.0.1b3/zarchive_py.egg-info/PKG-INFO +24 -0
- zarchive_py-0.0.1b3/zarchive_py.egg-info/SOURCES.txt +41 -0
- zarchive_py-0.0.1b3/zarchive_py.egg-info/dependency_links.txt +1 -0
- zarchive_py-0.0.1b3/zarchive_py.egg-info/not-zip-safe +1 -0
- zarchive_py-0.0.1b3/zarchive_py.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zarchive-py
|
|
3
|
+
Version: 0.0.1b3
|
|
4
|
+
Summary: Python bindings for the zArchive library (identify, create, read, extract, verify).
|
|
5
|
+
Home-page: https://github.com/Leon-Zhang/lib_main
|
|
6
|
+
Author: Leon Zhang
|
|
7
|
+
Author-email: leonzh.work@outlook.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: C++
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/plain
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
zArchive reads and writes archive files (zip, gzip, bzip2, tar, 7z, cab, rar, and others): identify a file, open it, list and read members, extract, verify, and create new archives. This package calls that library from Python. The wheel includes the matching native library for the platform it was built on.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# zarchive-py
|
|
2
|
+
|
|
3
|
+
Python ctypes bindings for the **zArchive** native library. Install from PyPI as `zarchive-py`; import the module as `zarchive`. The package wraps the C API in `zArchive/CApi.h` plus the older exports:
|
|
4
|
+
|
|
5
|
+
- Identify archive paths (`zarchive_identify_*` / `ArchiveFSItemIdentifyA/W`)
|
|
6
|
+
- Open, list, read, verify, and extract (`zarchive_open_w`, `zarchive_enum_items`, `zarchive_read_file_alloc_w`, `zarchive_extract_*`, `zarchive_verify_*`)
|
|
7
|
+
- Create archives (`zarchive_create_begin_w`, `zarchive_add_*`, `zarchive_create_finish`, `zarchive_create_empty_w`)
|
|
8
|
+
- `CreateCompressedFile` / `FreeCompressedFile` — allocate a format-specific `CArchive*` (zip, gz, bz, tar, 7z, cab, za, ...)
|
|
9
|
+
- `zarchive_deinitialize` / `DeinitializeAll` — tear down native globals
|
|
10
|
+
|
|
11
|
+
`pip install zarchive-py` installs a **platform wheel** that already embeds the native `zArchive` library under `zarchive/_bin`. pip selects the wheel that matches the install OS/arch. No manual copy of `zArchive.dll` / `libzArchive.so` / `libzArchive.dylib` is required after install.
|
|
12
|
+
|
|
13
|
+
Published wheel tags:
|
|
14
|
+
|
|
15
|
+
| Platform | Wheel tag | Native library |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Windows x64 | `win_amd64` | `zArchive.dll` |
|
|
18
|
+
| Linux x86_64 | `manylinux_2_28_x86_64` or `manylinux_2_35_x86_64` | `libzArchive.so` |
|
|
19
|
+
| Linux aarch64 | `manylinux_*_aarch64` | `libzArchive.so` |
|
|
20
|
+
| macOS Intel | `macosx_11_0_x86_64` | `libzArchive.dylib` |
|
|
21
|
+
| macOS Apple Silicon | `macosx_11_0_arm64` | `libzArchive.dylib` |
|
|
22
|
+
| iOS device | `ios_13_0_arm64_iphoneos` | `libzArchive.so` |
|
|
23
|
+
| iOS simulator | `ios_13_0_arm64_iphonesimulator` or `ios_13_0_x86_64_iphonesimulator` | `libzArchive.so` |
|
|
24
|
+
| Android | `android_21_arm64_v8a` (also `armeabi_v7a`, `x86_64`, `x86`) | `libzArchive.so` |
|
|
25
|
+
|
|
26
|
+
The published `sdist` is source-only and cannot be installed without a native build.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Python 3.8+ (`python` or `python3` on `PATH`)
|
|
31
|
+
- A matching platform wheel. The published `sdist` is source-only.
|
|
32
|
+
- To **build** a wheel: a native `zArchive` shared library for that OS/arch (see below). The build copies it into the package.
|
|
33
|
+
|
|
34
|
+
DLL / shared-library search order at runtime:
|
|
35
|
+
|
|
36
|
+
1. Extra paths passed to `resolve_dll_path`
|
|
37
|
+
2. The installed package's `zarchive/_bin` directory
|
|
38
|
+
3. Under `$WORKSPACE_PATH` and `$WORKSPACE_PATH/lib_main` (`bin/x64`, `bin/linux_x86_64`, `bin/macos_arm64`, `bin/ios_arm64_iphoneos`, `bin/android_arm64_v8a`, …)
|
|
39
|
+
4. The same folders under the repo root
|
|
40
|
+
|
|
41
|
+
## Build the native library
|
|
42
|
+
|
|
43
|
+
Required only when creating a new wheel, not when installing from PyPI.
|
|
44
|
+
|
|
45
|
+
**Windows:** from this directory, `build_native.bat` (or `powershell -File build_native.ps1`) builds `zArchive.vcxproj` as **Dynamic_Release|x64**. Output: `%WORKSPACE_PATH%\bin\x64\zArchive.dll`. Set `ZARCHIVE_VS_CONFIG=Dynamic_Debug` for `zArchived.dll`. Debug wheels also need sibling engine DLLs in `bin\x64` and the CUDA 12 runtime (`cudart64_12.dll`). Import libraries under `%WORKSPACE_PATH%\lib\x64` must already exist; this script builds the zArchive project, not the rest of the solution.
|
|
46
|
+
|
|
47
|
+
**Linux / macOS:** on that OS, from this directory:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
chmod +x build_native.sh
|
|
51
|
+
./build_native.sh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`build_native.sh` does not run on Windows. It runs the CMake project at `build/cmake/zArchive` for the machine it is on (no cross-compile) and installs:
|
|
55
|
+
|
|
56
|
+
- Linux x86_64: `bin/linux_x86_64/libzArchive.so` (and `bin/linux/`)
|
|
57
|
+
- Linux aarch64: `bin/linux_aarch64/libzArchive.so`
|
|
58
|
+
- macOS Intel: `bin/macos_x86_64/libzArchive.dylib` (and `bin/macos/`)
|
|
59
|
+
- macOS Apple Silicon: `bin/macos_arm64/libzArchive.dylib`
|
|
60
|
+
|
|
61
|
+
**iOS** (macOS with Xcode) and **Android** (NDK) are cross builds. They produce `libzArchive.so` and do not replace the host library.
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
chmod +x build_native_ios.sh build_native_android.sh
|
|
65
|
+
./build_native_ios.sh
|
|
66
|
+
./build_native_android.sh
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- iOS device: `bin/ios_arm64_iphoneos/libzArchive.so` (`ZARCHIVE_IOS_SDK=iphoneos`, deployment target 13.0)
|
|
70
|
+
- iOS simulator: `bin/ios_arm64_iphonesimulator/libzArchive.so` (`ZARCHIVE_IOS_SDK=iphonesimulator`)
|
|
71
|
+
- Android arm64: `bin/android_arm64_v8a/libzArchive.so` (`ZARCHIVE_ANDROID_ABI=arm64-v8a`, API 21)
|
|
72
|
+
|
|
73
|
+
On Windows the Android NDK build is `build_native_android.bat`. Install and smoke tests for those wheels run only under CPython with `sys.platform` of `ios` or `android`:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
./devops/ios/_ios_install_test.sh
|
|
77
|
+
./devops/android/_android_install_test.sh
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
CMake needs a C++14 compiler. The shared library compiles zArchive plus sibling engine sources (`ZARCHIVE_EMBED_DEPS=ON`). Build each desktop architecture on a machine of that architecture. iOS and Android use the cross scripts above.
|
|
81
|
+
|
|
82
|
+
Ubuntu packages the script can pick up: `python3-dev`, `libmysqlclient-dev`, `liblua5.3-dev`, `mono-devel`, and `libnode-dev`. On macOS the same script looks up Homebrew (`python@3`, `mysql-client`, `lua`, `mono`, `node`) and the active `python3` include path, and sets `MACOSX_DEPLOYMENT_TARGET` to `11.0` when it is unset so the dylib matches the `macosx_11_0_*` wheel tag. Ogg/Vorbis headers ship in `extlib`.
|
|
83
|
+
|
|
84
|
+
GitHub Actions workflow `.github/workflows/zarchive-wheels.yml` builds the Linux and macOS wheels on `ubuntu-22.04`, `macos-13`, and `macos-14`.
|
|
85
|
+
|
|
86
|
+
## Build the Python package
|
|
87
|
+
|
|
88
|
+
From this directory (`build/Python/zArchive`).
|
|
89
|
+
|
|
90
|
+
Windows:
|
|
91
|
+
|
|
92
|
+
```bat
|
|
93
|
+
build_package.bat
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Linux / macOS:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
chmod +x build_package.sh
|
|
100
|
+
./build_package.sh
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
That stages the native library into `zarchive/_bin`, then writes:
|
|
104
|
+
|
|
105
|
+
- `dist/zarchive_py-0.0.1-py3-none-<platform>.whl` (embeds native libs)
|
|
106
|
+
- `dist/zarchive_py-0.0.1.tar.gz` (source only)
|
|
107
|
+
|
|
108
|
+
Equivalent: `build_package.ps1` or `python build_wheels.py`. A `py3-none-any` wheel is not produced.
|
|
109
|
+
|
|
110
|
+
To package extra wheels from natives already sitting under `bin/linux_*` or `bin/macos_*` (even on another OS):
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
python build_wheels.py --all
|
|
114
|
+
# or one target
|
|
115
|
+
python build_wheels.py --plat manylinux_2_28_x86_64 --native-dir ../../../../bin/linux_x86_64
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Override the wheel tag with `ZARCHIVE_WHEEL_PLAT` and the library folder with `ZARCHIVE_NATIVE_DIR`. On Linux, `ZARCHIVE_MANYLINUX` defaults to `manylinux_2_28`.
|
|
119
|
+
|
|
120
|
+
Install the wheel into the current interpreter:
|
|
121
|
+
|
|
122
|
+
```bat
|
|
123
|
+
python -m pip install --force-reinstall dist\zarchive_py-0.0.1-py3-none-win_amd64.whl
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
python3 -m pip install --force-reinstall dist/zarchive_py-0.0.1-py3-none-*.whl
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
After uploading every platform wheel to the same PyPI version, `pip install zarchive-py` picks the file for the current OS.
|
|
131
|
+
|
|
132
|
+
## Publish to PyPI
|
|
133
|
+
|
|
134
|
+
Scripts live in `build/Python` (default package folder is `zArchive`). Set `TWINE_USERNAME=__token__` and `TWINE_PASSWORD` to a PyPI/TestPyPI API token, or write `%USERPROFILE%\.pypirc`.
|
|
135
|
+
|
|
136
|
+
```bat
|
|
137
|
+
cd /d ..
|
|
138
|
+
publish_check.bat
|
|
139
|
+
publish_testpypi.bat
|
|
140
|
+
publish_pypi.bat
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
cd ..
|
|
145
|
+
./publish_check.sh
|
|
146
|
+
./publish_testpypi.sh
|
|
147
|
+
./publish_pypi.sh
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Production upload prompts for `YES` unless `PYPI_UPLOAD_CONFIRM=YES`. The PyPI project name is `zarchive-py`. After upload:
|
|
151
|
+
|
|
152
|
+
```bat
|
|
153
|
+
python -m pip install zarchive-py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Run tests
|
|
157
|
+
|
|
158
|
+
Uses the bundled native library in `zarchive/_bin` after a package build, or a workspace build via `WORKSPACE_PATH`.
|
|
159
|
+
|
|
160
|
+
```bat
|
|
161
|
+
test_package.bat
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
chmod +x test_package.sh
|
|
166
|
+
./test_package.sh
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Each case prints its description, key values, and PASS/FAIL/SKIP/ERROR, then a module summary. Same output via:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
python tests/test_zarchive.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
A short smoke script is also available:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
python test_smoke.py
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Tests that need the native library are skipped if it is not found in `zarchive/_bin` or on the search path.
|
|
182
|
+
|
|
183
|
+
## Usage
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from zarchive import (
|
|
187
|
+
Archive,
|
|
188
|
+
ArchiveReader,
|
|
189
|
+
ArchiveWriter,
|
|
190
|
+
GzArchive,
|
|
191
|
+
SevenZArchive,
|
|
192
|
+
ZipArchive,
|
|
193
|
+
extract,
|
|
194
|
+
format_from_path,
|
|
195
|
+
format_infos,
|
|
196
|
+
identify,
|
|
197
|
+
supported_formats,
|
|
198
|
+
get_library,
|
|
199
|
+
resolve_dll_path,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
print(resolve_dll_path())
|
|
203
|
+
print(identify(r"C:\data\pack.zip"))
|
|
204
|
+
print(supported_formats())
|
|
205
|
+
print(format_infos())
|
|
206
|
+
print(format_from_path(r"C:\data\pack.7z"))
|
|
207
|
+
|
|
208
|
+
with ArchiveReader(r"C:\data\pack.tar") as arc:
|
|
209
|
+
print(arc.namelist())
|
|
210
|
+
print(arc.read("readme.txt"))
|
|
211
|
+
arc.extractall(r"C:\data\out")
|
|
212
|
+
|
|
213
|
+
extract(r"C:\data\pack.gz", r"C:\data\out2")
|
|
214
|
+
|
|
215
|
+
# Extension selects the format when archive_type is ARCHIVE_AUTO (the default).
|
|
216
|
+
with ArchiveWriter(r"C:\data\new.7z") as writer:
|
|
217
|
+
writer.writestr("hello.txt", "hello")
|
|
218
|
+
writer.write(r"C:\data\file.bin", "file.bin")
|
|
219
|
+
|
|
220
|
+
with GzArchive.create(r"C:\data\new.gz") as writer:
|
|
221
|
+
writer.writestr("hello.txt", "hello")
|
|
222
|
+
|
|
223
|
+
with ZipArchive() as zf:
|
|
224
|
+
print(hex(zf.handle))
|
|
225
|
+
with SevenZArchive() as z7:
|
|
226
|
+
print(hex(z7.handle))
|
|
227
|
+
|
|
228
|
+
get_library().deinitialize_all()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`WORKSPACE_PATH` is only needed when running from a source checkout that has not staged `zarchive/_bin`:
|
|
232
|
+
|
|
233
|
+
```bat
|
|
234
|
+
set WORKSPACE_PATH=d:\Workspace
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
export WORKSPACE_PATH="$HOME/Workspace"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
On Windows, the loader adds the DLL directory and CUDA `bin` folders (`CUDA_PATH`, `CUDA_PATH_V*`, or a `PATH` entry that contains `cudart64_12.dll`) via `os.add_dll_directory`. Debug `zCoded` / `zArchive` builds need the CUDA 12 runtime. On Linux it prepends `LD_LIBRARY_PATH`; on macOS, `DYLD_LIBRARY_PATH`.
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Copy the built zArchive shared library and its local deps into zarchive/_bin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from _plat import (
|
|
14
|
+
family_from_wheel_tag,
|
|
15
|
+
host_family,
|
|
16
|
+
host_wheel_tag,
|
|
17
|
+
native_search_dirs,
|
|
18
|
+
primary_library_names,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_HERE = Path(__file__).resolve().parent
|
|
22
|
+
_BIN_DIR = _HERE / "zarchive" / "_bin"
|
|
23
|
+
_REPO_ROOT = _HERE.parents[2]
|
|
24
|
+
|
|
25
|
+
_SYSTEM_LIBS = {
|
|
26
|
+
"kernel32.dll",
|
|
27
|
+
"user32.dll",
|
|
28
|
+
"gdi32.dll",
|
|
29
|
+
"ole32.dll",
|
|
30
|
+
"oleaut32.dll",
|
|
31
|
+
"advapi32.dll",
|
|
32
|
+
"shell32.dll",
|
|
33
|
+
"shlwapi.dll",
|
|
34
|
+
"ws2_32.dll",
|
|
35
|
+
"comctl32.dll",
|
|
36
|
+
"comdlg32.dll",
|
|
37
|
+
"cabinet.dll",
|
|
38
|
+
"ntdll.dll",
|
|
39
|
+
"bcrypt.dll",
|
|
40
|
+
"crypt32.dll",
|
|
41
|
+
"winhttp.dll",
|
|
42
|
+
"wininet.dll",
|
|
43
|
+
"imm32.dll",
|
|
44
|
+
"msvcrt.dll",
|
|
45
|
+
"ucrtbase.dll",
|
|
46
|
+
"vcruntime140.dll",
|
|
47
|
+
"vcruntime140_1.dll",
|
|
48
|
+
"msvcp140.dll",
|
|
49
|
+
"rpcrt4.dll",
|
|
50
|
+
"setupapi.dll",
|
|
51
|
+
"version.dll",
|
|
52
|
+
"winmm.dll",
|
|
53
|
+
"iphlpapi.dll",
|
|
54
|
+
"dbghelp.dll",
|
|
55
|
+
"psapi.dll",
|
|
56
|
+
"powrprof.dll",
|
|
57
|
+
"wlanapi.dll",
|
|
58
|
+
"python3.dll",
|
|
59
|
+
"python311.dll",
|
|
60
|
+
"python310.dll",
|
|
61
|
+
"python39.dll",
|
|
62
|
+
"python38.dll",
|
|
63
|
+
"libsystem.b.dylib",
|
|
64
|
+
"libc.so.6",
|
|
65
|
+
"libm.so.6",
|
|
66
|
+
"libdl.so.2",
|
|
67
|
+
"libpthread.so.0",
|
|
68
|
+
"librt.so.1",
|
|
69
|
+
"ld-linux-x86-64.so.2",
|
|
70
|
+
"ld-linux-aarch64.so.1",
|
|
71
|
+
"libgcc_s.so.1",
|
|
72
|
+
"libstdc++.so.6",
|
|
73
|
+
"libcxx.so.1",
|
|
74
|
+
"libc++.1.dylib",
|
|
75
|
+
"libSystem.B.dylib",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_SIBLING_PREFIXES = (
|
|
79
|
+
"zArchive",
|
|
80
|
+
"zMemLib",
|
|
81
|
+
"zPlugin",
|
|
82
|
+
"ude",
|
|
83
|
+
"SIMDProcessor",
|
|
84
|
+
"zThread",
|
|
85
|
+
"zTask",
|
|
86
|
+
"cpuidt",
|
|
87
|
+
"zDebug",
|
|
88
|
+
"zScript",
|
|
89
|
+
"zCode",
|
|
90
|
+
"zDataStorage",
|
|
91
|
+
"zDevEnv",
|
|
92
|
+
"zPlatform",
|
|
93
|
+
"zCompiler",
|
|
94
|
+
"CodeGen",
|
|
95
|
+
"zMaths",
|
|
96
|
+
"COMHelper",
|
|
97
|
+
"zLocale",
|
|
98
|
+
"zArcMain",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def package_bin_dir() -> Path:
|
|
103
|
+
return _BIN_DIR
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _target_family() -> str:
|
|
107
|
+
override = os.environ.get("ZARCHIVE_WHEEL_PLAT")
|
|
108
|
+
if override:
|
|
109
|
+
return family_from_wheel_tag(override)
|
|
110
|
+
return host_family()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _primary_names() -> list[str]:
|
|
114
|
+
return primary_library_names(_target_family())
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _plat_dirs() -> tuple[str, ...]:
|
|
118
|
+
return native_search_dirs(_target_family())
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _search_roots() -> list[Path]:
|
|
122
|
+
roots: list[Path] = []
|
|
123
|
+
workspace = os.environ.get("WORKSPACE_PATH")
|
|
124
|
+
if workspace:
|
|
125
|
+
roots.append(Path(workspace))
|
|
126
|
+
roots.append(Path(workspace) / "lib_main")
|
|
127
|
+
roots.append(_REPO_ROOT)
|
|
128
|
+
seen: set[str] = set()
|
|
129
|
+
unique: list[Path] = []
|
|
130
|
+
for root in roots:
|
|
131
|
+
key = os.path.normcase(str(root.resolve())) if root.exists() else os.path.normcase(str(root))
|
|
132
|
+
if key not in seen:
|
|
133
|
+
seen.add(key)
|
|
134
|
+
unique.append(root)
|
|
135
|
+
return unique
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _explicit_native_dir() -> Optional[Path]:
|
|
139
|
+
raw = os.environ.get("ZARCHIVE_NATIVE_DIR")
|
|
140
|
+
if not raw:
|
|
141
|
+
return None
|
|
142
|
+
path = Path(raw)
|
|
143
|
+
return path if path.is_dir() else None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def find_primary_library() -> Optional[Path]:
|
|
147
|
+
explicit = _explicit_native_dir()
|
|
148
|
+
if explicit is not None:
|
|
149
|
+
for name in _primary_names():
|
|
150
|
+
candidate = explicit / name
|
|
151
|
+
if candidate.is_file():
|
|
152
|
+
return candidate
|
|
153
|
+
for item in explicit.iterdir():
|
|
154
|
+
if item.is_file() and item.suffix.lower() in {".dll", ".so", ".dylib"}:
|
|
155
|
+
if "zarchive" in item.name.lower():
|
|
156
|
+
return item
|
|
157
|
+
for root in _search_roots():
|
|
158
|
+
for rel in _plat_dirs():
|
|
159
|
+
directory = root / Path(rel)
|
|
160
|
+
for name in _primary_names():
|
|
161
|
+
candidate = directory / name
|
|
162
|
+
if candidate.is_file():
|
|
163
|
+
return candidate
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _is_system_lib(name: str) -> bool:
|
|
168
|
+
lowered = name.lower()
|
|
169
|
+
if lowered in _SYSTEM_LIBS:
|
|
170
|
+
return True
|
|
171
|
+
if lowered.startswith("api-ms-win-"):
|
|
172
|
+
return True
|
|
173
|
+
if lowered.startswith("vcruntime") or lowered.startswith("msvcp") or lowered.startswith("ucrtbase"):
|
|
174
|
+
return True
|
|
175
|
+
if lowered.startswith("libpython"):
|
|
176
|
+
return True
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _dumpbin_path() -> Optional[str]:
|
|
181
|
+
vswhere = Path(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")) / (
|
|
182
|
+
"Microsoft Visual Studio/Installer/vswhere.exe"
|
|
183
|
+
)
|
|
184
|
+
if not vswhere.is_file():
|
|
185
|
+
return None
|
|
186
|
+
try:
|
|
187
|
+
out = subprocess.check_output(
|
|
188
|
+
[str(vswhere), "-latest", "-products", "*", "-find", r"**\dumpbin.exe"],
|
|
189
|
+
text=True,
|
|
190
|
+
stderr=subprocess.DEVNULL,
|
|
191
|
+
)
|
|
192
|
+
except (subprocess.CalledProcessError, OSError):
|
|
193
|
+
return None
|
|
194
|
+
fallback = None
|
|
195
|
+
for line in out.splitlines():
|
|
196
|
+
path = line.strip()
|
|
197
|
+
if not path or not Path(path).is_file():
|
|
198
|
+
continue
|
|
199
|
+
lowered = path.lower()
|
|
200
|
+
if lowered.endswith(r"\hostx64\x64\dumpbin.exe"):
|
|
201
|
+
return path
|
|
202
|
+
if "dumpbin.exe" in lowered:
|
|
203
|
+
fallback = path
|
|
204
|
+
return fallback
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _dependents_windows(dll: Path) -> list[str]:
|
|
208
|
+
dumpbin = _dumpbin_path()
|
|
209
|
+
if not dumpbin:
|
|
210
|
+
return []
|
|
211
|
+
try:
|
|
212
|
+
out = subprocess.check_output([dumpbin, "/dependents", str(dll)], text=True, stderr=subprocess.STDOUT)
|
|
213
|
+
except (subprocess.CalledProcessError, OSError):
|
|
214
|
+
return []
|
|
215
|
+
names: list[str] = []
|
|
216
|
+
capture = False
|
|
217
|
+
for line in out.splitlines():
|
|
218
|
+
if "Image has the following dependencies" in line:
|
|
219
|
+
capture = True
|
|
220
|
+
continue
|
|
221
|
+
if capture and line.strip() == "Summary":
|
|
222
|
+
break
|
|
223
|
+
match = re.match(r"\s+(\S+\.dll)\s*$", line, re.I)
|
|
224
|
+
if capture and match:
|
|
225
|
+
names.append(match.group(1))
|
|
226
|
+
return names
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _dependents_unix(library: Path) -> list[str]:
|
|
230
|
+
if sys.platform == "darwin":
|
|
231
|
+
cmd = ["otool", "-L", str(library)]
|
|
232
|
+
else:
|
|
233
|
+
cmd = ["ldd", str(library)]
|
|
234
|
+
try:
|
|
235
|
+
out = subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT)
|
|
236
|
+
except (subprocess.CalledProcessError, OSError, FileNotFoundError):
|
|
237
|
+
return []
|
|
238
|
+
names: list[str] = []
|
|
239
|
+
for line in out.splitlines():
|
|
240
|
+
if sys.platform == "darwin":
|
|
241
|
+
match = re.search(r"^\s+(\S+\.dylib)", line)
|
|
242
|
+
else:
|
|
243
|
+
match = re.search(r"(\S+\.so(?:\.\d+)*)\s+=>\s+(\S+)", line)
|
|
244
|
+
if match and match.group(2) not in {"not", "found"}:
|
|
245
|
+
names.append(os.path.basename(match.group(2)))
|
|
246
|
+
continue
|
|
247
|
+
match = re.search(r"^\s+(\S+\.so(?:\.\d+)*)", line)
|
|
248
|
+
if match:
|
|
249
|
+
names.append(os.path.basename(match.group(1)))
|
|
250
|
+
return names
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _can_inspect_library(library: Path) -> bool:
|
|
254
|
+
family = _target_family()
|
|
255
|
+
suffix = library.suffix.lower()
|
|
256
|
+
if family == "windows":
|
|
257
|
+
return os.name == "nt" and suffix == ".dll"
|
|
258
|
+
if family == "macos":
|
|
259
|
+
return sys.platform == "darwin" and suffix == ".dylib"
|
|
260
|
+
if family in {"android", "ios"}:
|
|
261
|
+
return False
|
|
262
|
+
return os.name != "nt" and sys.platform != "darwin" and ".so" in library.name
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _copy_all_shared_from(directory: Path) -> list[Path]:
|
|
266
|
+
files: list[Path] = []
|
|
267
|
+
for item in directory.iterdir():
|
|
268
|
+
if not item.is_file():
|
|
269
|
+
continue
|
|
270
|
+
name = item.name.lower()
|
|
271
|
+
if name.endswith((".dll", ".dylib")) or ".so" in name:
|
|
272
|
+
files.append(item)
|
|
273
|
+
return files
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _fix_loader_paths() -> None:
|
|
277
|
+
"""Make bundled Unix libraries look next to themselves for siblings."""
|
|
278
|
+
family = _target_family()
|
|
279
|
+
if family == "windows" or os.name == "nt":
|
|
280
|
+
return
|
|
281
|
+
if family == "macos" and sys.platform == "darwin":
|
|
282
|
+
for item in _BIN_DIR.iterdir():
|
|
283
|
+
if not item.is_file() or item.suffix != ".dylib":
|
|
284
|
+
continue
|
|
285
|
+
subprocess.run(
|
|
286
|
+
["install_name_tool", "-id", f"@loader_path/{item.name}", str(item)],
|
|
287
|
+
check=False,
|
|
288
|
+
stdout=subprocess.DEVNULL,
|
|
289
|
+
stderr=subprocess.DEVNULL,
|
|
290
|
+
)
|
|
291
|
+
subprocess.run(
|
|
292
|
+
["install_name_tool", "-add_rpath", "@loader_path", str(item)],
|
|
293
|
+
check=False,
|
|
294
|
+
stdout=subprocess.DEVNULL,
|
|
295
|
+
stderr=subprocess.DEVNULL,
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
if family == "linux" and os.name != "nt" and sys.platform != "darwin":
|
|
299
|
+
patchelf = shutil.which("patchelf")
|
|
300
|
+
if not patchelf:
|
|
301
|
+
return
|
|
302
|
+
for item in _BIN_DIR.iterdir():
|
|
303
|
+
if not item.is_file() or ".so" not in item.name:
|
|
304
|
+
continue
|
|
305
|
+
subprocess.run(
|
|
306
|
+
[patchelf, "--set-rpath", "$ORIGIN", str(item)],
|
|
307
|
+
check=False,
|
|
308
|
+
stdout=subprocess.DEVNULL,
|
|
309
|
+
stderr=subprocess.DEVNULL,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _dependents(library: Path) -> list[str]:
|
|
314
|
+
if os.name == "nt":
|
|
315
|
+
return _dependents_windows(library)
|
|
316
|
+
return _dependents_unix(library)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _cuda_runtime_dirs() -> list[Path]:
|
|
320
|
+
dirs: list[Path] = []
|
|
321
|
+
for key, value in os.environ.items():
|
|
322
|
+
if key == "CUDA_PATH" or key.startswith("CUDA_PATH_V"):
|
|
323
|
+
dirs.append(Path(value) / "bin")
|
|
324
|
+
for part in os.environ.get("PATH", "").split(os.pathsep):
|
|
325
|
+
if part and (Path(part) / "cudart64_12.dll").is_file():
|
|
326
|
+
dirs.append(Path(part))
|
|
327
|
+
extra = Path(r"E:\ProgramFiles\nVidia\CUDA\v12.6\bin")
|
|
328
|
+
if extra.is_dir():
|
|
329
|
+
dirs.append(extra)
|
|
330
|
+
return dirs
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _copy_optional_runtime(name: str, dest: Path) -> None:
|
|
334
|
+
if (dest / name).is_file():
|
|
335
|
+
return
|
|
336
|
+
search: list[Path] = []
|
|
337
|
+
if name.lower().startswith("cudart"):
|
|
338
|
+
search.extend(_cuda_runtime_dirs())
|
|
339
|
+
for directory in search:
|
|
340
|
+
src = directory / name
|
|
341
|
+
if src.is_file():
|
|
342
|
+
shutil.copy2(src, dest / name)
|
|
343
|
+
print(f" bundled runtime {name}")
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _sibling_fallback(primary: Path) -> list[Path]:
|
|
348
|
+
files: list[Path] = []
|
|
349
|
+
suffix = primary.suffix
|
|
350
|
+
for item in primary.parent.iterdir():
|
|
351
|
+
if not item.is_file() or item.suffix.lower() != suffix.lower():
|
|
352
|
+
continue
|
|
353
|
+
if any(item.name.startswith(prefix) for prefix in _SIBLING_PREFIXES):
|
|
354
|
+
files.append(item)
|
|
355
|
+
return files
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _clear_bin_dir() -> None:
|
|
359
|
+
_BIN_DIR.mkdir(parents=True, exist_ok=True)
|
|
360
|
+
for item in _BIN_DIR.iterdir():
|
|
361
|
+
if item.name == ".gitkeep":
|
|
362
|
+
continue
|
|
363
|
+
if item.is_file():
|
|
364
|
+
item.unlink()
|
|
365
|
+
elif item.is_dir():
|
|
366
|
+
shutil.rmtree(item)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def stage_native_libs() -> Path:
|
|
370
|
+
"""Copy the primary library and local deps into zarchive/_bin. Raises if missing."""
|
|
371
|
+
primary = find_primary_library()
|
|
372
|
+
if primary is None:
|
|
373
|
+
raise FileNotFoundError(
|
|
374
|
+
"Native zArchive library not found. Build the shared library for this "
|
|
375
|
+
"platform (Windows: build_native.ps1; Linux/macOS: build_native.sh; "
|
|
376
|
+
"iOS: build_native_ios.sh; Android: build_native_android.sh), "
|
|
377
|
+
"or set WORKSPACE_PATH / ZARCHIVE_NATIVE_DIR."
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
_clear_bin_dir()
|
|
381
|
+
copied: dict[str, Path] = {}
|
|
382
|
+
source_dir = primary.parent
|
|
383
|
+
inspect = _can_inspect_library(primary)
|
|
384
|
+
queue = [primary]
|
|
385
|
+
if not inspect:
|
|
386
|
+
queue.extend(_copy_all_shared_from(source_dir))
|
|
387
|
+
|
|
388
|
+
while queue:
|
|
389
|
+
current = queue.pop(0)
|
|
390
|
+
key = current.name.lower()
|
|
391
|
+
if key in copied:
|
|
392
|
+
continue
|
|
393
|
+
dest = _BIN_DIR / current.name
|
|
394
|
+
shutil.copy2(current, dest)
|
|
395
|
+
copied[key] = dest
|
|
396
|
+
print(f" bundled {current.name}")
|
|
397
|
+
if not inspect:
|
|
398
|
+
continue
|
|
399
|
+
for dep_name in _dependents(current):
|
|
400
|
+
if _is_system_lib(dep_name):
|
|
401
|
+
continue
|
|
402
|
+
local = source_dir / dep_name
|
|
403
|
+
if local.is_file():
|
|
404
|
+
queue.append(local)
|
|
405
|
+
else:
|
|
406
|
+
_copy_optional_runtime(dep_name, _BIN_DIR)
|
|
407
|
+
|
|
408
|
+
if inspect and len(copied) == 1:
|
|
409
|
+
for sibling in _sibling_fallback(primary):
|
|
410
|
+
key = sibling.name.lower()
|
|
411
|
+
if key not in copied:
|
|
412
|
+
shutil.copy2(sibling, _BIN_DIR / sibling.name)
|
|
413
|
+
copied[key] = sibling
|
|
414
|
+
print(f" bundled sibling {sibling.name}")
|
|
415
|
+
|
|
416
|
+
_fix_loader_paths()
|
|
417
|
+
print(f" wheel tag: {host_wheel_tag()}")
|
|
418
|
+
|
|
419
|
+
if not any((_BIN_DIR / name).is_file() for name in _primary_names()):
|
|
420
|
+
raise FileNotFoundError(f"Failed to stage {primary.name} into {_BIN_DIR}")
|
|
421
|
+
return _BIN_DIR
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def bundled_primary_names() -> list[str]:
|
|
425
|
+
return [name for name in _primary_names() if (_BIN_DIR / name).is_file()]
|