feat: Add GitHub Actions workflow for building and testing the Depth Anything 3 extension
- Create a build workflow in `.github/workflows/build.yml` to automate the build process. - Configure Python environment and install dependencies. - Add steps for running tests and packaging the extension. chore: Initialize project metadata and dependencies for Depth Anything 3 - Add `pyproject.toml` for project configuration and dependencies. - Include optional dependencies for app and gs functionalities. - Define project scripts and metadata. chore: Add requirements file for Depth Anything 3 dependencies - Create `requirements.txt` listing all necessary packages for the project. feat: Implement export utility functions for Depth Anything 3 - Create `src/depth_anything_3/utils/export/__init__.py` to handle various export formats. - Implement functions for exporting predictions in different formats. feat: Add ray utility functions for homography estimation - Create `src/depth_anything_3/utils/ray_utils.py` with functions for computing optimal rotation and homography using RANSAC. feat: Implement vendor preparation script - Add `prepare_vendor.py` to manage third-party dependencies and artifacts. - Include functions for downloading and verifying artifacts based on a lock file. test: Add unit tests for vendor preparation script - Create `tests/test_prepare_vendor.py` to test artifact downloading and verification. - Implement tests for handling hash mismatches and unsupported platforms. chore: Add vendor lock file for dependency management - Create `vendor.lock.toml` to define artifacts and platforms for the project.
This commit is contained in:
106
pack.py
106
pack.py
@@ -1,3 +1,4 @@
|
||||
import hashlib
|
||||
import io
|
||||
import tarfile
|
||||
import tomllib
|
||||
@@ -7,28 +8,64 @@ from zipfile import ZIP_DEFLATED, ZipFile
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||
SOURCE_DIR = PROJECT_ROOT / "src" / "snapform"
|
||||
SOURCES_DIR = PROJECT_ROOT / "sources"
|
||||
VENDOR_DIR = PROJECT_ROOT / "vendor"
|
||||
DIST_DIR = PROJECT_ROOT / "dist"
|
||||
PATCHES_DIR = SOURCES_DIR / "patches"
|
||||
PATCHES_DIR = PROJECT_ROOT / "patches"
|
||||
LOCK_PATH = PROJECT_ROOT / "vendor.lock.toml"
|
||||
|
||||
|
||||
def load_vendor_lock():
|
||||
"""Return the locked third-party artifact metadata."""
|
||||
with LOCK_PATH.open("rb") as stream:
|
||||
lock = tomllib.load(stream)
|
||||
if lock.get("schema_version") != 1:
|
||||
raise RuntimeError("Unsupported vendor.lock.toml schema")
|
||||
return lock
|
||||
|
||||
|
||||
VENDOR_LOCK = load_vendor_lock()
|
||||
PLATFORMS = {
|
||||
"windows-x64": "uv-x86_64-pc-windows-msvc.zip",
|
||||
"macos-x64": "uv-x86_64-apple-darwin.tar.gz",
|
||||
"macos-arm64": "uv-aarch64-apple-darwin.tar.gz",
|
||||
"linux-x64": "uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||
platform: artifact["filename"]
|
||||
for platform, artifact in VENDOR_LOCK["platforms"].items()
|
||||
}
|
||||
BUNDLED_ARCHIVES = {
|
||||
"sources/depth-anything-3.tar.gz": "depth-anything-3.tar.gz",
|
||||
"sources/ben2.tar.gz": "ben2.tar.gz",
|
||||
f"vendor/{artifact['filename']}": artifact["filename"]
|
||||
for artifact in VENDOR_LOCK["artifacts"].values()
|
||||
}
|
||||
OVERLAYS = {
|
||||
"depth-anything-3.tar.gz": "depth-anything-3",
|
||||
artifact["filename"]: artifact["patches"]
|
||||
for artifact in VENDOR_LOCK["artifacts"].values()
|
||||
if "patches" in artifact
|
||||
}
|
||||
|
||||
|
||||
def bundled_source_bytes(filename):
|
||||
"""返回内置源码包内容;patches/<overlay>/ 目录中的文件会覆盖官方包对应路径。"""
|
||||
archive_path = SOURCES_DIR / filename
|
||||
def file_sha256(path):
|
||||
"""Return the SHA-256 digest for a file."""
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def validate_artifact(path, expected_sha256):
|
||||
"""Require an artifact to exist and match its locked digest."""
|
||||
if not path.is_file():
|
||||
raise RuntimeError(
|
||||
f"Missing vendor artifact: {path}. "
|
||||
"Run: uv run python prepare_vendor.py"
|
||||
)
|
||||
actual = file_sha256(path)
|
||||
if actual != expected_sha256:
|
||||
raise RuntimeError(
|
||||
f"Vendor artifact hash mismatch: {path}. "
|
||||
"Run: uv run python prepare_vendor.py"
|
||||
)
|
||||
|
||||
|
||||
def bundled_artifact_bytes(filename):
|
||||
"""Return a bundled artifact with its configured overlay applied."""
|
||||
archive_path = VENDOR_DIR / filename
|
||||
overlay_dir = PATCHES_DIR / OVERLAYS.get(filename, filename)
|
||||
if not overlay_dir.is_dir():
|
||||
return archive_path.read_bytes()
|
||||
@@ -71,10 +108,8 @@ def manifest_for_platform(platform):
|
||||
head = lines[:table_start]
|
||||
tail = lines[table_start:]
|
||||
head = [line for line in head if not line.strip().startswith("platforms =")]
|
||||
# Insert into the top-level section (before any `[table]` header) so the
|
||||
# field is part of the extension metadata, not a sub-key of a table.
|
||||
# Place it after the `blender_version_min` line, alongside the other
|
||||
# top-level keys (before `license`), keeping the file readable.
|
||||
# 插入顶层区域,避免 platforms 被解析为后续 table 的子键。
|
||||
# 放在 blender_version_min 后面,使扩展元数据保持易读。
|
||||
insert_at = next(
|
||||
(i + 1 for i, line in enumerate(head) if line.strip().startswith("blender_version_min")),
|
||||
len(head),
|
||||
@@ -84,17 +119,31 @@ def manifest_for_platform(platform):
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def bundled_source_files(platform):
|
||||
def bundled_vendor_files(platform):
|
||||
entries = {
|
||||
name: SOURCES_DIR / filename
|
||||
name: VENDOR_DIR / filename
|
||||
for name, filename in BUNDLED_ARCHIVES.items()
|
||||
}
|
||||
entries[f"sources/{PLATFORMS[platform]}"] = (
|
||||
SOURCES_DIR / PLATFORMS[platform]
|
||||
entries[f"vendor/{PLATFORMS[platform]}"] = (
|
||||
VENDOR_DIR / PLATFORMS[platform]
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
def validate_vendor_files(platform):
|
||||
"""Validate common archives and the selected uv archive."""
|
||||
for artifact in VENDOR_LOCK["artifacts"].values():
|
||||
validate_artifact(
|
||||
VENDOR_DIR / artifact["filename"],
|
||||
artifact["sha256"],
|
||||
)
|
||||
uv_artifact = VENDOR_LOCK["platforms"][platform]
|
||||
validate_artifact(
|
||||
VENDOR_DIR / uv_artifact["filename"],
|
||||
uv_artifact["sha256"],
|
||||
)
|
||||
|
||||
|
||||
def build_extension(platform):
|
||||
if platform not in PLATFORMS:
|
||||
raise ValueError(f"Unsupported platform: {platform}")
|
||||
@@ -103,29 +152,28 @@ def build_extension(platform):
|
||||
f"{manifest['name']}.v{manifest['version']}.{platform}.zip"
|
||||
)
|
||||
DIST_DIR.mkdir(exist_ok=True)
|
||||
sources = bundled_source_files(platform)
|
||||
missing = [str(path) for path in sources.values() if not path.is_file()]
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
"Missing bundled source files: " + ", ".join(missing)
|
||||
)
|
||||
vendor_files = bundled_vendor_files(platform)
|
||||
validate_vendor_files(platform)
|
||||
with ZipFile(output_path, "w", ZIP_DEFLATED) as archive:
|
||||
for source_path in sorted(SOURCE_DIR.rglob("*")):
|
||||
if not source_path.is_file():
|
||||
continue
|
||||
if "__pycache__" in source_path.parts:
|
||||
continue
|
||||
relative_path = source_path.relative_to(SOURCE_DIR)
|
||||
if relative_path.parts[0] == "vendor":
|
||||
continue
|
||||
if source_path.name == "blender_manifest.toml":
|
||||
continue
|
||||
archive.write(source_path, source_path.relative_to(SOURCE_DIR))
|
||||
archive.write(source_path, relative_path)
|
||||
archive.writestr(
|
||||
"blender_manifest.toml",
|
||||
manifest_for_platform(platform),
|
||||
)
|
||||
for archive_name, source_path in sources.items():
|
||||
for archive_name, source_path in vendor_files.items():
|
||||
archive.writestr(
|
||||
archive_name,
|
||||
bundled_source_bytes(source_path.name),
|
||||
bundled_artifact_bytes(source_path.name),
|
||||
)
|
||||
return output_path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user