Closed2

pip downloadの挙動

さわらさわら
% python                           
Python 3.11.10 (main, Oct 16 2024, 08:56:36) [Clang 18.1.8 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.run(["pip", "download", "literalai==0.1.201", "--only-binary=:all:"])
ERROR: Could not find a version that satisfies the requirement literalai==0.1.201 (from versions: 0.0.1, 0.0.101, 0.0.102)
ERROR: No matching distribution found for literalai==0.1.201

[notice] A new release of pip is available: 24.0 -> 25.1
[notice] To update, run: pip install --upgrade pip
CompletedProcess(args=['pip', 'download', 'literalai==0.1.201', '--only-binary=:all:'], returncode=1)
>>> res = subprocess.run(["pip", "download", "literalai==0.1.201", "--only-binary=:all:"], capture_output=True, text=True)
>>> res
CompletedProcess(args=['pip', 'download', 'literalai==0.1.201', '--only-binary=:all:'], returncode=1, stdout='', stderr='ERROR: Could not find a version that satisfies the requirement literalai==0.1.201 (from versions: 0.0.1, 0.0.101, 0.0.102)\nERROR: No matching distribution found for literalai==0.1.201\n\n[notice] A new release of pip is available: 24.0 -> 25.1\n[notice] To update, run: pip install --upgrade pip\n')
>>> res = subprocess.run(["pip", "download", "literalai==0.0.1", "--only-binary=:all:"], capture_output=True, text=True)
>>> res
CompletedProcess(args=['pip', 'download', 'literalai==0.0.1', '--only-binary=:all:'], returncode=0, stdout='Collecting literalai==0.0.1\n  Using cached literalai-0.0.1-py3-none-any.whl.metadata (243 bytes)\nCollecting packaging>=23.0 (from literalai==0.0.1)\n  Using cached packaging-25.0-py3-none-any.whl.metadata (3.3 kB)\nCollecting httpx<0.25.0,>=0.23.0 (from literalai==0.0.1)\n  Using cached httpx-0.24.1-py3-none-any.whl.metadata (7.4 kB)\nCollecting pydantic<3,>=1 (from literalai==0.0.1)\n  Using cached pydantic-2.11.3-py3-none-any.whl.metadata (65 kB)\nCollecting certifi (from httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Using cached certifi-2025.4.26-py3-none-any.whl.metadata (2.5 kB)\nCollecting httpcore<0.18.0,>=0.15.0 (from httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Downloading httpcore-0.17.3-py3-none-any.whl.metadata (18 kB)\nCollecting idna (from httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Using cached idna-3.10-py3-none-any.whl.metadata (10 kB)\nCollecting sniffio (from httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Using cached sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)\nCollecting annotated-types>=0.6.0 (from pydantic<3,>=1->literalai==0.0.1)\n  Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\nCollecting pydantic-core==2.33.1 (from pydantic<3,>=1->literalai==0.0.1)\n  Using cached pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl.metadata (6.8 kB)\nCollecting typing-extensions>=4.12.2 (from pydantic<3,>=1->literalai==0.0.1)\n  Using cached typing_extensions-4.13.2-py3-none-any.whl.metadata (3.0 kB)\nCollecting typing-inspection>=0.4.0 (from pydantic<3,>=1->literalai==0.0.1)\n  Using cached typing_inspection-0.4.0-py3-none-any.whl.metadata (2.6 kB)\nCollecting h11<0.15,>=0.13 (from httpcore<0.18.0,>=0.15.0->httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Using cached h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\nCollecting anyio<5.0,>=3.0 (from httpcore<0.18.0,>=0.15.0->httpx<0.25.0,>=0.23.0->literalai==0.0.1)\n  Using cached anyio-4.9.0-py3-none-any.whl.metadata (4.7 kB)\nDownloading literalai-0.0.1-py3-none-any.whl (30 kB)\nDownloading httpx-0.24.1-py3-none-any.whl (75 kB)\n   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 75.4/75.4 kB 3.2 MB/s eta 0:00:00\nUsing cached packaging-25.0-py3-none-any.whl (66 kB)\nUsing cached pydantic-2.11.3-py3-none-any.whl (443 kB)\nUsing cached pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl (1.9 MB)\nUsing cached annotated_types-0.7.0-py3-none-any.whl (13 kB)\nDownloading httpcore-0.17.3-py3-none-any.whl (74 kB)\n   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 74.5/74.5 kB 5.9 MB/s eta 0:00:00\nUsing cached sniffio-1.3.1-py3-none-any.whl (10 kB)\nUsing cached typing_extensions-4.13.2-py3-none-any.whl (45 kB)\nUsing cached typing_inspection-0.4.0-py3-none-any.whl (14 kB)\nUsing cached certifi-2025.4.26-py3-none-any.whl (159 kB)\nUsing cached idna-3.10-py3-none-any.whl (70 kB)\nUsing cached anyio-4.9.0-py3-none-any.whl (100 kB)\nUsing cached h11-0.14.0-py3-none-any.whl (58 kB)\nSaved ./literalai-0.0.1-py3-none-any.whl\nSaved ./httpx-0.24.1-py3-none-any.whl\nSaved ./packaging-25.0-py3-none-any.whl\nSaved ./pydantic-2.11.3-py3-none-any.whl\nSaved ./pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl\nSaved ./annotated_types-0.7.0-py3-none-any.whl\nSaved ./httpcore-0.17.3-py3-none-any.whl\nSaved ./sniffio-1.3.1-py3-none-any.whl\nSaved ./typing_extensions-4.13.2-py3-none-any.whl\nSaved ./typing_inspection-0.4.0-py3-none-any.whl\nSaved ./certifi-2025.4.26-py3-none-any.whl\nSaved ./idna-3.10-py3-none-any.whl\nSaved ./anyio-4.9.0-py3-none-any.whl\nSaved ./h11-0.14.0-py3-none-any.whl\nSuccessfully downloaded literalai httpx packaging pydantic pydantic-core annotated-types httpcore sniffio typing-extensions typing-inspection certifi idna anyio h11\n', stderr='\n[notice] A new release of pip is available: 24.0 -> 25.1\n[notice] To update, run: pip install --upgrade pip\n')
さわらさわら

動的にwheelをダウンロードしてビルドするスクリプト。不要になったので供養。

オリジナルは https://github.com/xhiroga/blender-mcp-senpai/blob/main/extension/build.py

import json
import os
import shutil
import subprocess
import tempfile
import tomllib
from dataclasses import dataclass

import bpy
import tomlkit

PLATFORMS = ["windows-x64", "linux-x64", "macos-arm64", "macos-x64"]
ZIP_TARGET_DIR = "mcp_senpai"
OUTPUT_DIR = "output"
DOCS_EXTENSIONS_DIR = "../docs/extensions"


@dataclass
class Platform:
    pypi_suffix: str
    metadata: str


windows_x64 = Platform(pypi_suffix="win_amd64", metadata="windows-x64")
linux_x64 = Platform(pypi_suffix="manylinux2014_x86_64", metadata="linux-x64")
macos_arm = Platform(pypi_suffix="macosx_12_0_arm64", metadata="macos-arm64")
macos_intel = Platform(pypi_suffix="macosx_10_16_x86_64", metadata="macos-x64")

platforms = [windows_x64, linux_x64, macos_arm, macos_intel]


def dependencies() -> list[str]:
    try:
        completed_proc = subprocess.run(
            [
                "uv",
                "export",
                "--package",
                "extension",
                "--no-annotate",
                "--no-hashes",
                "--no-header",
            ],
            capture_output=True,
            text=True,
            check=True,
        )
    except subprocess.CalledProcessError as e:
        print(f"{e.stderr=}")
        return []

    deps: list[str] = []
    for line in completed_proc.stdout.splitlines():
        if not line or line.startswith("#") or line.startswith((" ", "\t")):
            continue
        deps.append(line.split(";")[0].strip())
    return deps


def get_version() -> str:
    with open("pyproject.toml", mode="rb") as f:
        pyproject = tomllib.load(f)
        return pyproject["project"]["version"]


def get_pip() -> str:
    return "pip" if shutil.which("pip") is not None else "pip3"


def get_git_commit_hash() -> str:
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True
        )
        return result.stdout.strip()
    except subprocess.CalledProcessError:
        return ""


def download_wheel(
    dep: str,
    python_version: str,
    platform: Platform,
    wheels_dest_dir: str,
):
    print(f"{dep=}, {python_version=}, {platform=}, {wheels_dest_dir=}")
    pip = get_pip()
    download_bin_proc_completed = subprocess.run(
        [
            pip,
            "download",
            dep,
            "-d",
            wheels_dest_dir,
            "--only-binary=:all:",
            f"--python-version={python_version}",
            f"--platform={platform.pypi_suffix}",
            "--no-deps",  # deps are resolved by uv export
        ],
        capture_output=True,
        text=True,
    )

    ERROR_MESSAGE = "ERROR: Could not find a version that satisfies the requirement"
    if ERROR_MESSAGE in download_bin_proc_completed.stderr:
        with tempfile.TemporaryDirectory() as temp_dir:
            print(
                f"{dep=} is not found in index. Downloading source and building wheel..."
            )
            try:
                download_src_proc_completed = subprocess.run(
                    [
                        pip,
                        "download",
                        dep,
                        "--no-binary=:all:",
                        "-d",
                        temp_dir,
                        "--no-deps",
                    ],
                    capture_output=True,
                    text=True,
                    check=True,
                )
            except subprocess.CalledProcessError as e:
                print(f"{e.stderr=}")
                return

            tar_gz_file = None
            for line in download_src_proc_completed.stdout.splitlines():
                if "Saved" in line or "File was already downloaded" in line:
                    tar_gz_file = line.split("/")[-1]

            if tar_gz_file is None:
                print(f"{dep=} is not found in {temp_dir=}")
                return

            source_files = os.path.join(temp_dir, tar_gz_file)

            try:
                wheel_completed = subprocess.run(
                    [
                        pip,
                        "wheel",
                        "--no-deps",
                        "--wheel-dir",
                        wheels_dest_dir,
                        source_files,
                    ],
                    capture_output=True,
                    text=True,
                    check=True,
                )
                print(f"{wheel_completed.stdout=}")

            except subprocess.CalledProcessError as e:
                print(f"{e.stderr=}")
                return


def generate_blender_manifest():
    with open("blender_manifest_template.toml", mode="r") as f:
        manifest = tomlkit.load(f)

    manifest["platforms"] = [platform.metadata for platform in platforms]
    manifest["commit"] = get_git_commit_hash()
    manifest["version"] = get_version()

    manifest["wheels"] = [
        f"./wheels/{f}"
        for f in os.listdir(f"{ZIP_TARGET_DIR}/wheels")
        if f.endswith(".whl")
    ]

    with open(f"{ZIP_TARGET_DIR}/blender_manifest.toml", mode="w") as f:
        tomlkit.dump(manifest, f)


def build():
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    # On macOS or WSL, bpy.app.binary_path is empty.
    blender_exe = os.environ.get("BLENDER_EXE") or bpy.app.binary_path
    print(f"{blender_exe=}")
    subprocess.run(
        [
            blender_exe,
            "--factory-startup",
            "--command",
            "extension",
            "build",
            "--split-platforms",
            "--source-dir",
            ZIP_TARGET_DIR,
            "--output-dir",
            OUTPUT_DIR,
        ],
    )


def index_json():
    blender_exe = os.environ.get("BLENDER_EXE") or bpy.app.binary_path
    print(f"{blender_exe=}")
    subprocess.run(
        [
            blender_exe,
            "--factory-startup",
            "--command",
            "extension",
            "server-generate",
            "--repo-dir",
            OUTPUT_DIR,
        ],
    )


def deploy_json():
    os.makedirs(DOCS_EXTENSIONS_DIR, exist_ok=True)

    with open(f"{OUTPUT_DIR}/index.json", "r") as f:
        index_data = json.load(f)

    version = get_version()

    for item in index_data.get("data", []):
        if "archive_url" in item:
            filename = os.path.basename(item["archive_url"])

            item["archive_url"] = (
                f"https://github.com/xhiroga/blender-mcp-senpai/releases/download/v{version}/{filename}"
            )

    with open(f"{DOCS_EXTENSIONS_DIR}/index.json", "w") as f:
        json.dump(index_data, f, indent=2)


def main():
    wheels_dest_dir = f"{ZIP_TARGET_DIR}/wheels"
    os.makedirs(wheels_dest_dir, exist_ok=True)

    deps = dependencies()
    python_version = "3.11"
    for platform in platforms:
        for dep in deps:
            download_wheel(dep, python_version, platform, wheels_dest_dir)

    generate_blender_manifest()

    build()

    index_json()

    deploy_json()


if __name__ == "__main__":
    main()
このスクラップは4ヶ月前にクローズされました