语境

我有一个 Python 项目,我为其包装了一些 C/C++ 代码(使用出色的 PyBind 库)。我有一组 C 和 Python 单元测试,并且我已经配置了 Gitlab 的 CI 以在每次推送时运行它们。
C 测试使用名为 minunit 的极简单元测试框架,我使用 Python 的单元测试套件。

在运行 C 测试之前,先编译所有 C 代码,然后进行测试。我还想在运行 Python 测试之前为 Python 编译 C/C++ 包装器,但很难做到。

几句话问

在运行单元测试之前,是否有标准/好的方法让 Gitlab-CI 使用 setuptools 构建 Python 扩展?

带有更多字词的问题/我尝试过的内容的描述

为了在本地编译 C/C++ 包装器,我将 setuptools 与包含 setup.py 命令的 build_ext 文件一起使用。
我使用 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

但是,不是 sudo 在 gitlab 的服务器上,我收到以下错误 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)

最佳答案

我的建议是将整个测试运行逻辑移到设置脚本中。

使用 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_suitetests_requiretest_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 命令

最后一个要求是运行 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 调用外部命令)。剩下的就是通过在设置函数中传递 test 用自定义命令替换默认的 cmdclass 命令。

现在您已经在设置脚本中收集了所有内容,python setup.py test 将完成所有肮脏的工作。



我对 Gitlab CI 没有任何经验,但我无法想象在构建服务器上安装包是不可能的。也许这个问题会有所帮助:How to use sudo in build script for gitlab ci?

如果真的没有其他选择,你可以 bootstrap a local copy of setuptools with ez_setup.py 。但是请注意,尽管此方法仍然有效,但 it was deprecated recently

此外,如果您碰巧使用最新版本的 Python(3.4 和更新版本),那么您应该将 pip 与 Python 发行版捆绑在一起,因此应该可以在没有 root 权限的情况下安装 setuptools
$ python -m pip install --user setuptools

关于python - 获取 Gitlab 的持续集成以编译用 C 编写的 Python 扩展,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50721732/

10-13 09:24
查看更多