Сборка нескольких колес во время выполнения - PullRequest
0 голосов
/ 23 января 2020

Я хочу создавать колеса во время выполнения, которые содержат некоторые сценарии, а также некоторые полезные данные. Например, в этом примере папка target содержит две простые сборки build_123 и build_124, которые должны быть упакованы как колесо.

main_project
├── __init__.py
├── whl_util.py               # wheel building script posted below
target/
├── build_123/                # contains one build to be packaged as a whl
│   └── mypkg
|       ├── __init__.py
│       ├── data
|       |   ├── __init__.py
|       |   └── mat.json
│       └── main
|           ├── __init__.py
|           └── dumpmat.py
└── build_124/                # contains another build to be packaged as a whl
    └── mypkg
        ├── ...

В моем сценарии эти колеса используются в качестве выходного формата и упаковки это не основной прогресс. Упаковка колеса должна рассматриваться как простая операция ввода-вывода, которая читает папку сборки и выводит колесо без каких-либо побочных эффектов, кроме этого. Для выполнения этой задачи я придумал следующее решение:

# main_project/whl_util.py
from setuptools import setup, find_packages
import sys
import shutil
import os

def bdist_wheel(build_dir=".", dist_dir=None):
    # backing up argv to restore them afterwards
    argv_bak = sys.argv[:]

    # clear args from running script with "bdist_wheel"
    file = sys.argv[0]
    sys.argv.clear()
    sys.argv.extend([file, "bdist_wheel"])

    if dist_dir is not None and "--dist-dir" not in sys.argv:
        sys.argv.extend(["--dist-dir", dist_dir])

    sys.argv.extend(["clean", "--all"])

    setup(
        name="mypkg",
        version=0.1,
        packages=find_packages(build_dir),
        install_requires=[],
        include_package_data=True,
        package_dir={'': build_dir},
        package_data={"mypkg.data": ["mat.json"]}
    )

    # restore args
    sys.argv.clear()
    sys.argv.extend(argv_bak)


def main():
    # Adding main method here for testing.
    # As mentioned in my actual scenario the wheels should be built as an output format at runtime
    print("BUILD 123")
    bdist_wheel("target/build_123", dist_dir="target/dist_123")
    print("BUILD 124")
    bdist_wheel("target/build_124", dist_dir="target/dist_124")

if __name__ == "__main__":
    main()

Мне также не очень нравится способ передачи параметров в setuptools через sys.argv, но, похоже, это единственный путь. Однако основная проблема заключается в том, что первое колесо построено нормально, а при втором вызове bdist_wheel / setup возникает ошибка:

python3 -m main_project.whl_util
BUILD 123
running bdist_wheel
running build
running build_py
creating build
creating build/lib
creating build/lib/mypkg
copying target/build_123/mypkg/__init__.py -> build/lib/mypkg
creating build/lib/mypkg/data
copying target/build_123/mypkg/data/__init__.py -> build/lib/mypkg/data
creating build/lib/mypkg/main
copying target/build_123/mypkg/main/__init__.py -> build/lib/mypkg/main
copying target/build_123/mypkg/main/dumpmat.py -> build/lib/mypkg/main
running egg_info
writing target/build_123/mypkg.egg-info/PKG-INFO
writing dependency_links to target/build_123/mypkg.egg-info/dependency_links.txt
writing top-level names to target/build_123/mypkg.egg-info/top_level.txt
writing manifest file 'target/build_123/mypkg.egg-info/SOURCES.txt'
copying target/build_123/mypkg/data/mat.json -> build/lib/mypkg/data
installing to build/bdist.linux-x86_64/wheel
running install
running install_lib
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/wheel
creating build/bdist.linux-x86_64/wheel/mypkg
copying build/lib/mypkg/__init__.py -> build/bdist.linux-x86_64/wheel/mypkg
creating build/bdist.linux-x86_64/wheel/mypkg/data
copying build/lib/mypkg/data/__init__.py -> build/bdist.linux-x86_64/wheel/mypkg/data
copying build/lib/mypkg/data/mat.json -> build/bdist.linux-x86_64/wheel/mypkg/data
creating build/bdist.linux-x86_64/wheel/mypkg/main
copying build/lib/mypkg/main/__init__.py -> build/bdist.linux-x86_64/wheel/mypkg/main
copying build/lib/mypkg/main/dumpmat.py -> build/bdist.linux-x86_64/wheel/mypkg/main
running install_egg_info
Copying target/build_123/mypkg.egg-info to build/bdist.linux-x86_64/wheel/mypkg-0.1-py3.7.egg-info
running install_scripts
creating build/bdist.linux-x86_64/wheel/mypkg-0.1.dist-info/WHEEL
creating 'target/dist_123/mypkg-0.1-py3-none-any.whl' and adding 'build/bdist.linux-x86_64/wheel' to it
adding 'mypkg/__init__.py'
adding 'mypkg/data/__init__.py'
adding 'mypkg/data/mat.json'
adding 'mypkg/main/__init__.py'
adding 'mypkg/main/dumpmat.py'
adding 'mypkg-0.1.dist-info/METADATA'
adding 'mypkg-0.1.dist-info/WHEEL'
adding 'mypkg-0.1.dist-info/top_level.txt'
adding 'mypkg-0.1.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
BUILD 124
running bdist_wheel
running build
running build_py
copying target/build_124/mypkg/__init__.py -> build/lib/mypkg
error: could not create 'build/lib/mypkg/__init__.py': No such file or directory

Process finished with exit code 1

Ошибка no such file or directory предполагает, что setuptools Модуль отслеживает, какие папки он уже создал, и предполагает, что эти папки все еще существуют. Однако после сборки первого колеса сценарий clean удалит встроенную папку (что необходимо, так как в противном случае setuptools будет повторно использовать папку, не очищая ее)

Мое единственное работающее решение - это форкнуть процесс перед применением setup:

pid = os.fork()
if pid == 0:
    setup(...)
    sys.exit(0)
os.waitpid(pid, 0)

Но так как это кажется очень грязным, и мой основной процесс очень интенсивно использует память, я бы предпочел избежать этого метода.

Так что мой Основной вопрос: Есть ли способ построить колесо без каких-либо побочных эффектов? Или есть способ сбросить состояние модуля setuptools после применения setup? В оптимальном мире я хотел бы создать колесо в памяти PyFilesystem и записывать только колесо на диск.

1 Ответ

1 голос
/ 23 января 2020

Я не уверен, setuptools предназначен для использования таким образом. Насколько я знаю pip и со. ( wheel , setuptools , et c.) На самом деле не имеют опубликованных c API или, по крайней мере, не дружественных.

distlib библиотека выглядит как многообещающая альтернатива с реальным API. См. distlib документацию "Использование API колеса" .

Если это не сработает, то, вероятно, я бы попробовал один из них:

subprocess.check_call([sys.executable, '-m', 'wheel', 'pack', 'target/build123'])

См. wheel pack документацию .

subprocess.check_call([sys.executable, '-m', 'pip', 'wheel', 'target/build123'])

См. pip wheel документацию . А причины, по которым его нельзя использовать с вызовами API, указаны в в разделе «Использование pip из вашей программы» документации pip


Несколько схожий вопрос с некоторыми интересными идеями:

Может быть, генерация setup.py динамически во время выполнения может помочь.

...