Получите непрерывную интеграцию Gitlab для компиляции расширения Python, написанного на C

Контекст

У меня есть проект Python, для которого я оборачиваю некоторый код C / C++ (используя отличную библиотеку PyBind). У меня есть набор модульных тестов C и Python, и я настроил CI Gitlab для их запуска при каждом нажатии. В тестах C используется минималистичный фреймворк модульного тестирования под названием minunit, а я использую набор Python unittest.

Перед запуском тестов C весь код C компилируется, а затем тестируется. Я также хотел бы скомпилировать оболочку C / C++ для Python перед запуском тестов Python, но у меня нет времени на это.

Вопрос в двух словах

Есть ли стандартный / хороший способ заставить Gitlab-CI создать расширение Python с использованием setuptools перед запуском модульных тестов?

Вопрос с большим количеством слов / Описание того, что я пробовал

Для локальной компиляции оболочки C / C++ я использую setuptools с файлом setup.py, включая команду build_ext. Я локально компилирую все с помощью python setup.py build_ext --inplace (последний аргумент --inplace просто скопирует скомпилированный файл в текущий каталог). Насколько я знаю, это вполне стандартно.

Что я пытался сделать в Gitlab, так это иметь скрипт Python (код ниже), который будет запускать несколько команд с помощью команды os.system (что кажется плохой практикой ...). Первая команда - запустить сборку сценария и запустить все тесты C. Это работает, но я рад принять рекомендации (следует ли мне настроить Gitlab CI для запуска тестов C отдельно?).

Теперь проблема возникает, когда я пытаюсь создать оболочку C / C++ с помощью os.system("cd python/ \npython setup.py build_ext --inplace"). Это вызывает ошибку

File "setup.py", line 1, in <module>
        from setuptools import setup, Extension
    ImportError: No module named setuptools

Поэтому я попытался изменить файл конфигурации CI моего gitlab, чтобы установить 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)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
1 017
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предлагаю перенести всю логику выполнения теста в сценарий установки.

с помощью команды 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, но результат должен быть очень похож на то, что производит PyBind).

используя дополнительные ключевые слова

Еще одна функция, которая может оказаться полезной, - это дополнительные ключевые слова, которые добавляет setuptools: test_suite, tests_require, test_loader (документы). Вот пример встраивания пользовательского набора тестов, как в 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 сделает всю грязную работу.

But, not being sudo on the gitlab's server, I get the following error

У меня нет опыта работы с Gitlab CI, но я не могу представить, что нет возможности устанавливать пакеты на сервер сборки. Может этот вопрос будет полезен: Как использовать sudo в скрипте сборки для gitlab ci?

Если другого варианта действительно нет, можно Загрузите локальную копию setuptools с ez_setup.py. Однако обратите внимание, что хотя этот метод все еще работает, недавно он устарел.

Кроме того, если вы используете последнюю версию Python (3.4 и новее), тогда у вас должен быть pip в комплекте с дистрибутивом Python, поэтому должна быть возможность установить setuptools без прав root с помощью

$ python -m pip install --user setuptools

Большое спасибо за ответ! Однако мои тесты не находятся в том же каталоге, что и оболочка, и команда python setup.py test не находит их, что, как я полагаю, можно решить довольно легко. Проблема в том, что это решение по-прежнему предполагает возможность загрузки setuptools, что не относится к моей проблеме ... Спасибо за две другие ссылки, я проверю их и добавлю ответ, если найду ту, которая решает мою проблему. проблема.

Christian 06.06.2018 21:51

Вы можете настроить обнаружение теста в скрипте установки, в ответе есть пример этого. Если вы имеете в виду тесты C, я обновил ответ, спустившись в каталог тестов (через os.chdir).

hoefling 07.06.2018 10:58

Что касается вопроса с разрешением, не могли бы вы поделиться подробностями? Это система, в которой вы не являетесь пользователем root и не имеете никаких шансов получить разрешения sudo от вашего администратора? Можете проверить, работает ли sudo apt install случайно? Кроме того, какая версия Python предустановлена ​​/ предназначена (python -V)? Есть ли у вас pip для целевого дистрибутива Python (pip -V)? По-прежнему доступны некоторые другие варианты, такие как создание локальной установки Python из исходного кода и установка любых пакетов, которые вам нужны локально, или запуск нового контейнера докеров, в котором у вас будет root-доступ.

hoefling 07.06.2018 11:02

Спасибо, да, настройка обнаружения тестов кажется естественным вариантом, но все же проблема в том, что для этого требуется setuptools. Да, я попробовал с sudo apt install и получил ту же ошибку. К сожалению, нет, у меня нет доступа к sudo. Я не знаю, какая версия работает на сервере, это должен быть Python 3.5.4, но я дважды проверю! У меня есть pip, да.

Christian 07.06.2018 12:10

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

Christian 07.06.2018 12:14

Если у вас есть pip и даже последняя версия Python, использование докера только для установки setuptools является излишним - используйте pip install --user setuptools или python -m pip install --user setuptools, и все готово. Сначала проверьте, соответствуют ли pip и python целевым версиям, замените конкретными pip3.5 / python3.5 / и т. д.

hoefling 07.06.2018 12:35

Большое спасибо, добавление python -m pip install --user setuptools помогло! Вы можете изменить свой комментарий как ответ, и я отмечу его как правильный ответ. Спасибо большое

Christian 07.06.2018 22:55

Хорошо, рад, что смог помочь! Я упомянул этот метод в последнем абзаце ответа.

hoefling 08.06.2018 12:50

Другие вопросы по теме