import tomllib from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile PROJECT_ROOT = Path(__file__).resolve().parent SOURCE_DIR = PROJECT_ROOT / "src" / "snapform" DIST_DIR = PROJECT_ROOT / "dist" PLATFORMS = ("windows-x64", "macos-arm64", "linux-x64") def manifest_for_platform(platform): """Return the manifest text declaring only the given platform.""" text = (SOURCE_DIR / "blender_manifest.toml").read_text(encoding="utf-8") lines = text.splitlines() table_start = next( (i for i, line in enumerate(lines) if line.startswith("[")), len(lines), ) head = lines[:table_start] tail = lines[table_start:] head = [line for line in head if not line.strip().startswith("platforms =")] # 插入顶层区域,避免 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), ) head.insert(insert_at, f'platforms = ["{platform}"]') lines = head + tail return "\n".join(lines) + "\n" def build_extension(platform): if platform not in PLATFORMS: raise ValueError(f"Unsupported platform: {platform}") manifest = tomllib.loads(manifest_for_platform(platform)) output_path = DIST_DIR / ( f"{manifest['name']}.v{manifest['version']}.{platform}.zip" ) DIST_DIR.mkdir(exist_ok=True) 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 source_path.name == "blender_manifest.toml": continue archive.write(source_path, relative_path) archive.writestr( "blender_manifest.toml", manifest_for_platform(platform), ) return output_path def build_all(): return [build_extension(platform) for platform in PLATFORMS] if __name__ == "__main__": extension_paths = build_all() for extension_path in extension_paths: size_mib = extension_path.stat().st_size / (1024 * 1024) print(f"Built {extension_path} ({size_mib:.2f} MiB)")