获取 Gitlab 的持续集成来编译一个用 C 编写的 Python 扩展
Get Gitlab's Continuous Integration to compile a Python extension written in C
上下文
我有一个 Python 项目,我为其包装了一些 C/C++ 代码(使用优秀的 PyBind 库)。我有一组 C 和 Python 单元测试,并且我在每次推送时将 Gitlab 的 CI 配置为 运行。
C 测试使用称为 minunit 的极简单元测试框架,我使用 Python 的单元测试套件。
在运行进行C测试之前,编译所有C代码然后进行测试。我还想在 运行 测试 Python 之前为 Python 编译 C/C++ 包装器,但是很难做到。
一句话问
在 运行 单元测试之前,是否有 standard/good 方法让 Gitlab-CI
使用 setuptools
构建 Python 扩展?
问题用词较多/我尝试过的描述
为了在本地编译 C/C++ 包装器,我使用 setuptools
和一个包含 build_ext
命令的 setup.py
文件。
我在本地用 python setup.py build_ext --inplace
编译所有内容(最后一个参数 --inplace
只会将编译后的文件复制到当前目录)。
据我所知,这是相当标准的。
我试图在 Gitlab 上做的是有一个 Python 脚本(下面的代码),它将 运行 使用 os.system
命令的一些命令(这似乎是不好的做法...)。
第一个命令是 运行 脚本构建和 运行 宁所有 C 测试。这行得通,但我很乐意接受建议(我应该将 Gitlab CI 配置为 运行 C 测试吗?)。
现在,当我尝试使用 os.system("cd python/ \npython setup.py build_ext --inplace")
构建 C/C++ 包装器时,问题就来了。这会产生错误
File "setup.py", line 1, in <module>
from setuptools import setup, Extension
ImportError: No module named setuptools
所以我尝试修改我的gitlab的CI配置文件来安装python-dev。我的 .gitlab-ci.yml
看起来像
test:
script:
- apt-get install -y python-dev
- python run_tests.py
但是,在 gitlab 的服务器上没有被 sudo,我得到以下错误 E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
。
任何人都知道解决这个问题的方法,或者更好的方法来解决这个问题?
我们非常欢迎任何帮助!
run_tests.py 文件
import unittest
import os
from shutil import copyfile
import glob
class AllTests(unittest.TestCase):
def test_all(self):
# this automatically loads all tests in current dir
testsuite = unittest.TestLoader().discover('tests/Python_tests')
# run tests
result = unittest.TextTestRunner(verbosity=2).run(testsuite)
# send/print results
self.assertEqual(result.failures, [], 'Failure')
if __name__ == "__main__":
# run C tests
print(' ------------------------------------------------------ C TESTS')
os.system("cd tests/C_tests/ \nbash run_all.sh")
# now python tests
print(' ------------------------------------------------- PYTHON TESTS')
# first build and copy shared library compiled from C++ in the python test directory
# build lib
os.system("cd python/ \npython setup.py build_ext --inplace")
# copy lib it to right place
dest_dir = 'tests/Python_tests/'
for file in glob.glob(r'python/*.so'):
print('Copying file to test dir : ', file)
copyfile(file, dest_dir+file.replace('python/', ''))
# run Python tests
unittest.main(verbosity=0)
我的建议是将整个测试 运行ning 逻辑移动到设置脚本中。
使用 test
命令
首先,setuptools
发布了一个 test
命令,因此您可以 运行 通过 python setup.py test
进行测试。更妙的是,test
在幕后调用 build_ext
命令并放置构建的扩展,以便在测试中可以访问它们,因此您无需显式调用 python setup.py build_ext
:
$ python setup.py test
running test
running egg_info
creating so.egg-info
writing so.egg-info/PKG-INFO
writing dependency_links to so.egg-info/dependency_links.txt
writing top-level names to so.egg-info/top_level.txt
writing manifest file 'so.egg-info/SOURCES.txt'
reading manifest file 'so.egg-info/SOURCES.txt'
writing manifest file 'so.egg-info/SOURCES.txt'
running build_ext
building 'wrap_fib' extension
creating build
creating build/temp.linux-aarch64-3.6
aarch64-unknown-linux-gnu-gcc -pthread -fPIC -I/data/gentoo64/usr/include/python3.6m -c wrap_fib.c -o build/temp.linux-aarch64-3.6/wrap_fib.o
aarch64-unknown-linux-gnu-gcc -pthread -fPIC -I/data/gentoo64/usr/include/python3.6m -c cfib.c -o build/temp.linux-aarch64-3.6/cfib.o
creating build/lib.linux-aarch64-3.6
aarch64-unknown-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,--as-needed -L. build/temp.linux-aarch64-3.6/wrap_fib.o build/temp.linux-aarch64-3.6/cfib.o -L/data/gentoo64/usr/lib64 -lpython3.6m -o build/lib.linux-aarch64-3.6/wrap_fib.cpython-36m-aarch64-linux-gnu.so
copying build/lib.linux-aarch64-3.6/wrap_fib.cpython-36m-aarch64-linux-gnu.so ->
test_fib_0 (test_fib.FibonacciTests) ... ok
test_fib_1 (test_fib.FibonacciTests) ... ok
test_fib_10 (test_fib.FibonacciTests) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
(我使用了 Cython Book examples repository 中的代码来玩,但输出应该与 PyBind 生成的非常相似)。
使用额外的关键字
另一个可能派上用场的功能是 setuptools
添加的额外关键字:test_suite
、tests_require
、test_loader
(docs)。这是一个嵌入自定义测试套件的示例,就像您在 run_tests.py
:
中所做的那样
# setup.py
import unittest
from Cython.Build import cythonize
from setuptools import setup, Extension
exts = cythonize([Extension("wrap_fib", sources=["cfib.c", "wrap_fib.pyx"])])
def pysuite():
return unittest.TestLoader().discover('tests/python_tests')
if __name__ == '__main__':
setup(
name='so',
version='0.1',
ext_modules=exts,
test_suite='setup.pysuite'
)
扩展 test
命令
最后一个要求是 运行ning C 测试。我们可以通过覆盖 test
命令并从那里调用一些自定义代码来嵌入它们。这样做的好处是 distutils
提供了一个命令 API 具有许多有用的功能,例如复制文件或执行外部命令:
# setup.py
import os
import unittest
from Cython.Build import cythonize
from setuptools import setup, Extension
from setuptools.command.test import test as test_orig
exts = cythonize([Extension("wrap_fib", sources=["cfib.c", "wrap_fib.pyx"])])
class test(test_orig):
def run(self):
# run python tests
super().run()
# run c tests
self.announce('Running C tests ...')
pwd = os.getcwd()
os.chdir('tests/C_tests')
self.spawn(['bash', 'run_all.sh'])
os.chdir(pwd)
def pysuite():
return unittest.TestLoader().discover('tests/python_tests')
if __name__ == '__main__':
setup(
name='so',
version='0.1',
ext_modules=exts,
test_suite='setup.pysuite',
cmdclass={'test': test}
)
我扩展了原始的 test
命令,运行 在 python 单元测试完成后添加了一些额外的东西(注意通过 self.spawn
调用外部命令)。剩下的就是通过在设置函数中传递 cmdclass
将默认的 test
命令替换为自定义命令。
现在您已将所有内容收集到设置脚本中,python setup.py test
将完成所有脏活。
But, not being sudo on the gitlab's server, I get the following error
我没有任何使用 Gitlab 的经验CI,但我无法想象在构建服务器上安装包是不可能的。也许这个问题会有所帮助:How to use sudo in build script for gitlab ci?
如果真的没有别的选择,可以bootstrap a local copy of setuptools
with ez_setup.py
. Note, however, that although this method still works, it was deprecated recently。
此外,如果您碰巧使用 Python 的最新版本(3.4 和更新版本),那么您应该将 pip
与 Python 发行版捆绑在一起,因此应该可以使用
在没有 root 权限的情况下安装 setuptools
$ python -m pip install --user setuptools
上下文
我有一个 Python 项目,我为其包装了一些 C/C++ 代码(使用优秀的 PyBind 库)。我有一组 C 和 Python 单元测试,并且我在每次推送时将 Gitlab 的 CI 配置为 运行。 C 测试使用称为 minunit 的极简单元测试框架,我使用 Python 的单元测试套件。
在运行进行C测试之前,编译所有C代码然后进行测试。我还想在 运行 测试 Python 之前为 Python 编译 C/C++ 包装器,但是很难做到。
一句话问
在 运行 单元测试之前,是否有 standard/good 方法让 Gitlab-CI
使用 setuptools
构建 Python 扩展?
问题用词较多/我尝试过的描述
为了在本地编译 C/C++ 包装器,我使用 setuptools
和一个包含 build_ext
命令的 setup.py
文件。
我在本地用 python setup.py build_ext --inplace
编译所有内容(最后一个参数 --inplace
只会将编译后的文件复制到当前目录)。
据我所知,这是相当标准的。
我试图在 Gitlab 上做的是有一个 Python 脚本(下面的代码),它将 运行 使用 os.system
命令的一些命令(这似乎是不好的做法...)。
第一个命令是 运行 脚本构建和 运行 宁所有 C 测试。这行得通,但我很乐意接受建议(我应该将 Gitlab CI 配置为 运行 C 测试吗?)。
现在,当我尝试使用 os.system("cd python/ \npython setup.py build_ext --inplace")
构建 C/C++ 包装器时,问题就来了。这会产生错误
File "setup.py", line 1, in <module>
from setuptools import setup, Extension
ImportError: No module named setuptools
所以我尝试修改我的gitlab的CI配置文件来安装python-dev。我的 .gitlab-ci.yml
看起来像
test:
script:
- apt-get install -y python-dev
- python run_tests.py
但是,在 gitlab 的服务器上没有被 sudo,我得到以下错误 E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
。
任何人都知道解决这个问题的方法,或者更好的方法来解决这个问题?
我们非常欢迎任何帮助!
run_tests.py 文件
import unittest
import os
from shutil import copyfile
import glob
class AllTests(unittest.TestCase):
def test_all(self):
# this automatically loads all tests in current dir
testsuite = unittest.TestLoader().discover('tests/Python_tests')
# run tests
result = unittest.TextTestRunner(verbosity=2).run(testsuite)
# send/print results
self.assertEqual(result.failures, [], 'Failure')
if __name__ == "__main__":
# run C tests
print(' ------------------------------------------------------ C TESTS')
os.system("cd tests/C_tests/ \nbash run_all.sh")
# now python tests
print(' ------------------------------------------------- PYTHON TESTS')
# first build and copy shared library compiled from C++ in the python test directory
# build lib
os.system("cd python/ \npython setup.py build_ext --inplace")
# copy lib it to right place
dest_dir = 'tests/Python_tests/'
for file in glob.glob(r'python/*.so'):
print('Copying file to test dir : ', file)
copyfile(file, dest_dir+file.replace('python/', ''))
# run Python tests
unittest.main(verbosity=0)
我的建议是将整个测试 运行ning 逻辑移动到设置脚本中。
使用 test
命令
首先,setuptools
发布了一个 test
命令,因此您可以 运行 通过 python setup.py test
进行测试。更妙的是,test
在幕后调用 build_ext
命令并放置构建的扩展,以便在测试中可以访问它们,因此您无需显式调用 python setup.py build_ext
:
$ python setup.py test
running test
running egg_info
creating so.egg-info
writing so.egg-info/PKG-INFO
writing dependency_links to so.egg-info/dependency_links.txt
writing top-level names to so.egg-info/top_level.txt
writing manifest file 'so.egg-info/SOURCES.txt'
reading manifest file 'so.egg-info/SOURCES.txt'
writing manifest file 'so.egg-info/SOURCES.txt'
running build_ext
building 'wrap_fib' extension
creating build
creating build/temp.linux-aarch64-3.6
aarch64-unknown-linux-gnu-gcc -pthread -fPIC -I/data/gentoo64/usr/include/python3.6m -c wrap_fib.c -o build/temp.linux-aarch64-3.6/wrap_fib.o
aarch64-unknown-linux-gnu-gcc -pthread -fPIC -I/data/gentoo64/usr/include/python3.6m -c cfib.c -o build/temp.linux-aarch64-3.6/cfib.o
creating build/lib.linux-aarch64-3.6
aarch64-unknown-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,--as-needed -L. build/temp.linux-aarch64-3.6/wrap_fib.o build/temp.linux-aarch64-3.6/cfib.o -L/data/gentoo64/usr/lib64 -lpython3.6m -o build/lib.linux-aarch64-3.6/wrap_fib.cpython-36m-aarch64-linux-gnu.so
copying build/lib.linux-aarch64-3.6/wrap_fib.cpython-36m-aarch64-linux-gnu.so ->
test_fib_0 (test_fib.FibonacciTests) ... ok
test_fib_1 (test_fib.FibonacciTests) ... ok
test_fib_10 (test_fib.FibonacciTests) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
(我使用了 Cython Book examples repository 中的代码来玩,但输出应该与 PyBind 生成的非常相似)。
使用额外的关键字
另一个可能派上用场的功能是 setuptools
添加的额外关键字:test_suite
、tests_require
、test_loader
(docs)。这是一个嵌入自定义测试套件的示例,就像您在 run_tests.py
:
# setup.py
import unittest
from Cython.Build import cythonize
from setuptools import setup, Extension
exts = cythonize([Extension("wrap_fib", sources=["cfib.c", "wrap_fib.pyx"])])
def pysuite():
return unittest.TestLoader().discover('tests/python_tests')
if __name__ == '__main__':
setup(
name='so',
version='0.1',
ext_modules=exts,
test_suite='setup.pysuite'
)
扩展 test
命令
最后一个要求是 运行ning C 测试。我们可以通过覆盖 test
命令并从那里调用一些自定义代码来嵌入它们。这样做的好处是 distutils
提供了一个命令 API 具有许多有用的功能,例如复制文件或执行外部命令:
# setup.py
import os
import unittest
from Cython.Build import cythonize
from setuptools import setup, Extension
from setuptools.command.test import test as test_orig
exts = cythonize([Extension("wrap_fib", sources=["cfib.c", "wrap_fib.pyx"])])
class test(test_orig):
def run(self):
# run python tests
super().run()
# run c tests
self.announce('Running C tests ...')
pwd = os.getcwd()
os.chdir('tests/C_tests')
self.spawn(['bash', 'run_all.sh'])
os.chdir(pwd)
def pysuite():
return unittest.TestLoader().discover('tests/python_tests')
if __name__ == '__main__':
setup(
name='so',
version='0.1',
ext_modules=exts,
test_suite='setup.pysuite',
cmdclass={'test': test}
)
我扩展了原始的 test
命令,运行 在 python 单元测试完成后添加了一些额外的东西(注意通过 self.spawn
调用外部命令)。剩下的就是通过在设置函数中传递 cmdclass
将默认的 test
命令替换为自定义命令。
现在您已将所有内容收集到设置脚本中,python setup.py test
将完成所有脏活。
But, not being sudo on the gitlab's server, I get the following error
我没有任何使用 Gitlab 的经验CI,但我无法想象在构建服务器上安装包是不可能的。也许这个问题会有所帮助:How to use sudo in build script for gitlab ci?
如果真的没有别的选择,可以bootstrap a local copy of setuptools
with ez_setup.py
. Note, however, that although this method still works, it was deprecated recently。
此外,如果您碰巧使用 Python 的最新版本(3.4 和更新版本),那么您应该将 pip
与 Python 发行版捆绑在一起,因此应该可以使用
setuptools
$ python -m pip install --user setuptools