我正在使用
setuptools
和 pyproject.toml
设置一个 Python 包。 Python 代码依赖于需要与代码一起编译和安装的 C 库(这是一个 make
项目)。
我已经将一些适用于
pip install .
和 python -m build
的东西放在一起,以制作可分发的:
# pyproject.toml
[project]
name = "mypackage"
[build-system]
requires = ["setuptools >= 61.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["mypackage"]
package-dir = { "" = "src" }
# setup.py
from pathlib import Path
from setuptools import setup
from setuptools.command.install import install
from setuptools.command.develop import develop
from setuptools.command.build import build
import os
import subprocess
mylib_relative = "mylib"
mylib_root = Path(__file__).parent.absolute() / mylib_relative
def create_binaries():
subprocess.call(["make", "-C", mylib_relative])
def remove_binaries():
patterns = (
"*.a",
"**/*.o",
"*.bin",
"*.so",
)
for pattern in patterns:
for file in mylib_root.glob(pattern):
os.remove(file)
class CustomBuild(build):
def run(self):
print("\nCustomBuild!\n")
remove_binaries()
create_binaries()
super().run()
class CustomDevelop(develop):
def run(self):
print("\nCustomDevelop!\n")
remove_binaries()
create_binaries()
super().run()
class CustomInstall(install):
def run(self):
print("\n\nCustomInstall\n\n")
mylib_lib = mylib_root / "adslib.so"
mylib_dest = Path(self.install_lib)
if not mylib_dest.exists():
mylib_dest.mkdir(parents=True)
self.copy_file(
str(mylib_lib),
str(mylib_dest),
)
super().run()
setup(
cmdclass={
"build": CustomBuild,
"develop": CustomDevelop,
"install": CustomInstall,
},
)
但是,当我使用 pip 和
pip install -e . [-v]
进行可编辑安装时,该库未编译和安装,仅将 Python 源添加到 venv 路径中。但如果没有库,该包将无法工作。
你可以看到我已经在
develop
中添加了setup.py
命令,但看起来它根本没有被调用过。
如何自定义可编辑安装以首先编译我的库?
我找到了一个可以使用的解决方案/解决方法。在
pip install -e .
之后,包含您的包的目录(通常为 src/
)将被附加到 PATH(通过从我的包中打印 sys.path
进行测试)。
因此,如果我只是确保将编译的库放入
src/
,则在可编辑安装后它也将在 PATH 上可用,就像正常安装一样。如果 .so
文件位于 .gitignore
下,那么它不应该因为位于 src/
而不是库目录中而打扰任何人。
完整
setup.py
:
from pathlib import Path
from setuptools import setup
from setuptools.command.install import install
from setuptools.command.build_py import build_py
import os
import subprocess
src_folder = Path(__file__).parent.absolute() / "src"
# ^ This will be on PATH for editable install
mylib_folder = Path(__file__).parent.absolute() / "mylib"
mylib_file = src_folder / "mylib.so"
class CustomBuildPy(build_py):
"""Custom command for `build_py`.
This command class is used because it is always run, also for an editable install.
"""
@classmethod
def compile_mylib(cls):
"""Return `True` if mylib was actually compiled."""
cls._clean_library()
cls._compile_library()
@staticmethod
def _compile_library():
"""Use `make` to build mylib - build is done in-place."""
# Produce `mylib.so`:
subprocess.call(["make", "-C", "mylib"])
@staticmethod
def _clean_library():
"""Remove all compilation artifacts."""
patterns = (
"*.a",
"**/*.o",
"*.bin",
"*.so",
)
for pattern in patterns:
for file in mylib_folder.glob(pattern):
os.remove(file)
if mylib_file.is_file():
os.remove(mylib_file)
def run(self):
# Move .so file into src/ to have it on PATH:
self.compile_adslib()
self.move_file(
str(mylib_folder / "mylib.so"),
str(mylib_file),
)
super().run()
class CustomInstall(install):
"""Install compiled mylib (but only for Linux)."""
def run(self):
mylib_dest = Path(self.install_lib)
if not mylib_dest.exists():
mylib_dest.mkdir(parents=True)
self.copy_file(
str(mylib_file),
str(mylib_dest),
)
super().run()
setup(
cmdclass={
"build_py": CustomBuildPy,
"install": CustomInstall,
},
)
# See `pyproject.toml` for all package information
# Also see `MANIFEST.in`