- 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.
112 lines
3.5 KiB
Python
112 lines
3.5 KiB
Python
import argparse
|
|
import hashlib
|
|
import sys
|
|
import tomllib
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
|
LOCK_PATH = PROJECT_ROOT / "vendor.lock.toml"
|
|
VENDOR_DIR = PROJECT_ROOT / "vendor"
|
|
CHUNK_SIZE = 1024 * 1024
|
|
|
|
|
|
def load_lock(path=LOCK_PATH):
|
|
"""Load and validate the vendor lock file."""
|
|
with Path(path).open("rb") as stream:
|
|
lock = tomllib.load(stream)
|
|
if lock.get("schema_version") != 1:
|
|
raise RuntimeError("Unsupported vendor.lock.toml schema")
|
|
return lock
|
|
|
|
|
|
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(CHUNK_SIZE):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def download_artifact(artifact, destination_dir=VENDOR_DIR):
|
|
"""Download one locked artifact and verify its content."""
|
|
destination_dir = Path(destination_dir)
|
|
destination = destination_dir / artifact["filename"]
|
|
expected = artifact["sha256"]
|
|
if destination.is_file() and file_sha256(destination) == expected:
|
|
print(f"Ready: {destination.name}")
|
|
return destination
|
|
|
|
destination_dir.mkdir(parents=True, exist_ok=True)
|
|
temporary = destination.with_name(f"{destination.name}.part")
|
|
temporary.unlink(missing_ok=True)
|
|
print(f"Downloading: {destination.name}")
|
|
request = urllib.request.Request(
|
|
artifact["url"],
|
|
headers={"User-Agent": "SnapForm vendor preparation"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request) as response, temporary.open("wb") as output:
|
|
while chunk := response.read(CHUNK_SIZE):
|
|
output.write(chunk)
|
|
actual = file_sha256(temporary)
|
|
if actual != expected:
|
|
raise RuntimeError(
|
|
f"SHA-256 mismatch for {destination.name}: "
|
|
f"expected {expected}, got {actual}"
|
|
)
|
|
temporary.replace(destination)
|
|
except Exception:
|
|
temporary.unlink(missing_ok=True)
|
|
raise
|
|
return destination
|
|
|
|
|
|
def prepare(platforms=None, lock_path=LOCK_PATH, destination_dir=VENDOR_DIR):
|
|
"""Prepare common third-party archives for selected platforms."""
|
|
lock = load_lock(lock_path)
|
|
available = lock["platforms"]
|
|
selected = list(available) if platforms is None else platforms
|
|
unknown = sorted(set(selected) - set(available))
|
|
if unknown:
|
|
raise ValueError(f"Unsupported platform: {', '.join(unknown)}")
|
|
|
|
artifacts = list(lock["artifacts"].values())
|
|
artifacts.extend(available[platform] for platform in selected)
|
|
return [
|
|
download_artifact(artifact, destination_dir)
|
|
for artifact in artifacts
|
|
]
|
|
|
|
|
|
def parse_args(argv=None):
|
|
"""Parse command-line options."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Download and verify third-party files for SnapForm.",
|
|
)
|
|
parser.add_argument(
|
|
"--platform",
|
|
action="append",
|
|
dest="platforms",
|
|
help="Prepare one platform; repeat as needed. Default: all platforms.",
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv=None):
|
|
"""Run vendor preparation from the command line."""
|
|
args = parse_args(argv)
|
|
try:
|
|
paths = prepare(args.platforms)
|
|
except (OSError, RuntimeError, ValueError) as error:
|
|
print(f"Preparation failed: {error}", file=sys.stderr)
|
|
return 1
|
|
print(f"Prepared {len(paths)} files")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|