├── .github └── workflows │ ├── build.yaml │ ├── flatpak.yaml │ ├── functional.yml │ ├── publish.yaml │ └── stale.yaml ├── .gitignore ├── .readthedocs.yaml ├── .travis.yml-backup ├── CODEOWNERS ├── CONTRIBUTING.rst ├── COPYING ├── Makefile ├── README.rst ├── docker-wrapper.sh ├── docs ├── Makefile ├── conf.py ├── contributing.rst ├── ebuildtester.rst ├── index.rst ├── modules.rst └── requirements.txt ├── ebuildtester-wrapper.sh ├── ebuildtester.bash-completion ├── ebuildtester ├── __init__.py ├── atom.py ├── docker.py ├── main.py ├── options.py ├── parse.py └── utils.py ├── io.github.nicolasbock.ebuildtester.metainfo.xml ├── io.github.nicolasbock.ebuildtester.yaml ├── python3-modules.json ├── setup.cfg ├── setup.py ├── snap ├── hooks │ ├── configure │ └── install └── snapcraft.yaml ├── test-requirements.txt ├── test ├── __init__.py ├── test_atom.py ├── test_parse.py └── test_utils.py └── tox.ini /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Test ebuildtester 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | include: 18 | - os: ubuntu-22.04 19 | python-version: 3.8 20 | - os: ubuntu-22.04 21 | python-version: 3.9 22 | - os: ubuntu-22.04 23 | python-version: '3.10' 24 | - os: ubuntu-22.04 25 | python-version: '3.11' 26 | - os: ubuntu-22.04 27 | python-version: '3.12' 28 | runs-on: ${{ matrix.os }} 29 | name: Python ${{ matrix.os }} / ${{ matrix.python-version }} build 30 | steps: 31 | 32 | - name: Get tox environment 33 | run: | 34 | tox_environment="py${python_version/./}" 35 | echo "tox_environment=${tox_environment}" >> $GITHUB_ENV 36 | env: 37 | python_version: ${{ matrix.python-version }} 38 | 39 | - name: Check out sources 40 | uses: actions/checkout@v4 41 | with: 42 | fetch-depth: 0 43 | 44 | - name: Set up Python 45 | uses: actions/setup-python@v5 46 | with: 47 | python-version: ${{ matrix.python-version }} 48 | 49 | - name: Install tox 50 | run: sudo apt install --yes tox 51 | 52 | - name: Build and test 53 | run: tox -e ${{ env.tox_environment }} 54 | 55 | - name: Archive test results 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: test log (${{ env.tox_environment }}) 59 | path: .tox/${{ env.tox_environment }}/log 60 | if: failure() 61 | 62 | - name: Bandit 63 | run: tox -e bandit 64 | if: ${{ matrix.python-version == '3.10' }} 65 | 66 | - name: Archive bandit results 67 | uses: actions/upload-artifact@v4 68 | with: 69 | name: bandit log 70 | path: .tox/bandit/log 71 | if: ${{ matrix.python-version == '3.10' && failure() }} 72 | 73 | - name: Pep8 74 | run: tox -e pep8 75 | if: ${{ matrix.python-version == '3.10' }} 76 | 77 | - name: Build docs 78 | run: tox -e docs 79 | if: ${{ matrix.python-version == '3.10' }} 80 | 81 | - name: Test PyPi Package 82 | run: tox -e pypi 83 | if: ${{ matrix.python-version == '3.10' }} 84 | -------------------------------------------------------------------------------- /.github/workflows/flatpak.yaml: -------------------------------------------------------------------------------- 1 | name: Build flatpack package 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | 13 | build: 14 | runs-on: ubuntu-latest 15 | name: Build flatpak 16 | steps: 17 | 18 | - name: Check out sources 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up flatpak build tools 22 | run: | 23 | set -e -u -x 24 | 25 | sudo apt update 26 | sudo apt install flatpak flatpak-builder 27 | sudo flatpak remote-add --if-not-exists \ 28 | flathub https://flathub.org/repo/flathub.flatpakrepo 29 | sudo flatpak install --assumeyes flathub \ 30 | org.freedesktop.Platform//21.08 org.freedesktop.Sdk//21.08 31 | sudo flatpak install --assumeyes flathub org.freedesktop.appstream-glib 32 | - name: Verify metadata 33 | run: | 34 | flatpak run org.freedesktop.appstream-glib \ 35 | validate io.github.nicolasbock.ebuildtester.metainfo.xml 36 | - name: Build flatpak 37 | run: make flatpak 38 | -------------------------------------------------------------------------------- /.github/workflows/functional.yml: -------------------------------------------------------------------------------- 1 | name: Functional testing 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | functional_testing: 14 | runs-on: ubuntu-latest 15 | name: Functional testing 16 | steps: 17 | - name: Check out sources 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: '3.10' 26 | 27 | - name: Install 28 | run: | 29 | sudo apt install python3-pip 30 | pip list --verbose 31 | python3 setup.py install 32 | 33 | - name: Clone portage 34 | run: | 35 | pushd ${HOME} 36 | git clone --depth 1 https://github.com/gentoo/gentoo.git 37 | popd 38 | 39 | - name: Docker info 40 | run: docker info 41 | 42 | - name: Testbuild of `nano` 43 | run: | 44 | ebuildtester --portage-dir ~/gentoo \ 45 | --rm \ 46 | --pull \ 47 | --ccache /var/tmp/ccache \ 48 | --atom app-editors/nano 49 | 50 | - name: Test whether any container is still running 51 | run: docker ps --all 52 | 53 | - name: Archive test results 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: ebuildtester log 57 | path: | 58 | /tmp/ebuildtester*.log 59 | /var/tmp/ccache/ 60 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Publish PyPI Package 4 | 5 | on: 6 | release: 7 | types: [created] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | name: Publish PyPI Package 14 | steps: 15 | 16 | - name: Check out sources 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.10' 25 | 26 | - name: Get tox environment 27 | run: | 28 | tox_environment="py310" 29 | echo "tox_environment=${tox_environment}" >> $GITHUB_ENV 30 | 31 | - name: Install tox 32 | run: sudo apt install --yes tox 33 | 34 | - name: Publish PyPI Package 35 | env: 36 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 37 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 38 | run: | 39 | tox -e pypi 40 | .tox/pypi/bin/twine upload .tox/pypi/dist/* 41 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PR' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | workflow_dispatch: 6 | permissions: 7 | issues: write 8 | pull-requests: write 9 | 10 | jobs: 11 | stale: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/stale@v9 15 | with: 16 | stale-issue-message: >- 17 | This issue is stale because it has been open for 90 days with no 18 | activity. Remove the stale label or comment or this will be closed 19 | in 30 days. 20 | close-issue-message: >- 21 | This issue was closed because it has been stale for 90 days 22 | with no activity. 23 | days-before-issue-stale: 90 24 | days-before-issue-close: 30 25 | stale-pr-message: >- 26 | This PR is stale because it has been open for 90 days with no 27 | activity. 28 | close-pr-message: >- 29 | This PR was closed because it has been stale for 90 days 30 | with no activity. 31 | days-before-pr-stale: 90 32 | days-before-pr-close: -1 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | ebuildtester.egg-info/ 4 | *__pycache__* 5 | venv/ 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | python: 21 | install: 22 | - requirements: docs/requirements.txt 23 | - method: setuptools 24 | path: . 25 | -------------------------------------------------------------------------------- /.travis.yml-backup: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.6" 5 | - "3.7" 6 | - "3.8" 7 | 8 | install: 9 | - pip install -r requirements.txt 10 | 11 | script: 12 | - bandit ebuildtester 13 | - pycodestyle ebuildtester 14 | - python setup.py build 15 | - python setup.py test 16 | - python setup.py install 17 | - sphinx-apidoc --force --output-dir docs ebuildtester 18 | - sphinx-build -M doctest docs docs/_build 19 | - sphinx-build -M linkcheck docs docs/_build 20 | - sphinx-build -M coverage docs docs/_build 21 | - sphinx-build -M html docs docs/_build 22 | - sphinx-build -M man docs docs/_build 23 | 24 | deploy: 25 | provider: pypi 26 | user: nicolasbock 27 | password: 28 | secure: G9YozcC1lGoG1s2tndAUHpxH917vFkOP8BXEFSW4G1tLwmo09QcTIxmaYkqWxBHGwRJbA+68X6jvQPEluKEVD9wqJPBeFdUF9zjfl6zPdLZTRkPDucb1YLAvwCVZBgNlXjFGkp7qkVI0EDplrMVdtFR6V/xf4pt75fl9Qg/gQrKYKTWPJTKXK0YQQBwFoTERTFQNuTwjm9lb0aZ4/frn0QSWKxnUhT55oOE1yr4e+8pUFNUkQIydn29EtUo8us197Q7xtHGlF3kiMwebJ8ioh7LOg7n6pgLGq9oIJNRg5BlTcOPTffBvAHjsoDiQ+N+5Z6dbQgtAyIbEC3nk8Wvh+BjJ0xpyD65yH7n9dQsAX8HbmV2acgaWUrh0cgwvNjuMz6dZlK9LypHQbhY4Ewf9u0leF4WGlAR2IKhZ4aLYre/mzgDeGEd2IuIx3RUM+VtIscU36skYOn2k2XN83+4M1ITTr9sFvIr2S5LowPz2C1eTYI6Uf8kUSe8deZWc8ZT2uq3kQ8oWQWUfTFI3JG1cmNM/40OtT7WBTWUl7daV+xrdEtYVL68pAFjXTxrMAv33kBpj2MLbH1AAe9lECuu42IhTEf7lPeJ8qP/Q/P1ij+C8BIIN8yv0B4GeC+HrfMTXwB7WRGejvTIHDMBkqnTlJ5A7uwdtFdEd5BWP26jjZiU= 29 | distributions: "sdist bdist_wheel" 30 | skip_cleanup: false 31 | on: 32 | tags: true 33 | python: "3.6" 34 | branch: master 35 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nicolasbock 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Developer Documentation 2 | ======================= 3 | 4 | This project supports Python 3.8, 3.9, 3.10, 3.11 and 3.12. Other Python versions 5 | might work as well but are not regularly tested. 6 | 7 | For locally testing changes it is very handy to install `tox` which automates 8 | the creation of Python virtual environments. 9 | 10 | Dependencies 11 | ------------ 12 | 13 | - `docker` 14 | - `fuse` 15 | 16 | Setting up a developer environment 17 | ---------------------------------- 18 | 19 | .. code-block:: console 20 | 21 | $ python -m virtualenv venv 22 | $ source venv/bin/activate 23 | $ (venv) pip install -r requirements.txt 24 | 25 | Install `ebuildtester` in the `virtualenv`: 26 | 27 | .. code-block:: console 28 | 29 | $ (venv) python setup.py install 30 | 31 | Run the development version: 32 | 33 | .. code-block:: console 34 | 35 | $ (venv) ebuildtester ... 36 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2020 Nicolas Bock and contributors 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: distclean dist upload docs 2 | 3 | upload: dist 4 | twine upload dist/* 5 | 6 | dist: distclean 7 | python setup.py sdist 8 | python setup.py bdist_wheel --universal 9 | 10 | docs: 11 | sphinx-apidoc --force --output-dir docs ebuildtester 12 | $(MAKE) -C docs 13 | 14 | distclean: 15 | rm -rf dist/* 16 | 17 | flatpak: 18 | flatpak-builder --force-clean build-dir io.github.nicolasbock.ebuildtester.yaml 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | This is a tool to test a Gentoo ebuild and its dependencies. The idea is that 5 | the ebuild is emerged in a clean (and current) stage3 Docker container. 6 | 7 | .. image:: https://badge.fury.io/py/ebuildtester.svg 8 | :target: https://badge.fury.io/py/ebuildtester 9 | 10 | .. image:: https://readthedocs.org/projects/ebuildtester/badge/?version=latest 11 | :target: http://ebuildtester.readthedocs.io/en/latest/?badge=latest 12 | :alt: Documentation Status 13 | 14 | .. image:: https://github.com/nicolasbock/ebuildtester/actions/workflows/build.yaml/badge.svg 15 | :target: https://github.com/nicolasbock/ebuildtester/actions?query=workflow%3Abuild 16 | :alt: GitHub Actions 17 | 18 | .. image:: https://badges.gitter.im/ebuildtester/ebuildtester.svg 19 | :alt: Join the chat at https://gitter.im/ebuildtester/ebuildtester 20 | :target: https://gitter.im/ebuildtester/ebuildtester?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 21 | 22 | .. image:: https://snapcraft.io/ebuildtester/badge.svg 23 | :alt: Get it from the Snap Store 24 | :target: https://snapcraft.io/ebuildtester 25 | 26 | Requirements 27 | ------------ 28 | 29 | Using require `Docker `_ and `FUSE 30 | `_. 31 | 32 | If you plan to use specific storage driver options (by passing 33 | ``--storage-opt``), be aware that these are specific to the `configured Docker 34 | storage driver 35 | `__. Refer 36 | to the Docker documentation about `storage drivers 37 | `_ for more information. 38 | Particularly see the list of `options per storage driver 39 | `_. 40 | 41 | System-wide configuration of the storage driver used by Docker is done in 42 | ``/etc/docker/daemon.json``. For example, to select the `devicemapper 43 | `_ storage 44 | driver, specify: 45 | 46 | .. code-block:: javascript 47 | 48 | { 49 | "storage-driver": "devicemapper" 50 | } 51 | 52 | Usage 53 | ----- 54 | 55 | We are going to assume that the user has a local git clone of the portage tree in 56 | 57 | .. code-block:: console 58 | 59 | /usr/local/git/gentoo 60 | 61 | We have added a new ebuild and would like to verify that the build 62 | dependencies are all correct. We can build the package (ATOM) with: 63 | 64 | .. code-block:: console 65 | 66 | ebuildtester --portage-dir /usr/local/git/gentoo \ 67 | --atom ATOM \ 68 | --use USE1 USE2 69 | 70 | where we have specified two USE flags, USE1 and USE2. The 71 | `ebuildtester` command will now create a docker container and start 72 | installing the ATOM. All specified dependencies will be installed as 73 | well. 74 | 75 | 76 | Command line arguments 77 | ---------------------- 78 | 79 | The command understands the following command line arguments: 80 | 81 | .. code-block:: console 82 | 83 | usage: ebuildtester [-h] [--version] [--atom ATOM [ATOM ...]] [--binhost BINHOST] [--live-ebuild] 84 | [--manual] --portage-dir PORTAGE_DIR [--overlay-dir OVERLAY_DIR] [--update] 85 | [--install-basic-packages] [--threads N] [--use USE [USE ...]] 86 | [--global-use GLOBAL_USE [GLOBAL_USE ...]] [--unmask ATOM] [--unstable] 87 | [--gcc-version VER] [--python-single-target PYTHON_SINGLE_TARGET] 88 | [--python-targets PYTHON_TARGETS] [--rm] [--storage-opt STORAGE_OPT [STORAGE_OPT ...]] 89 | [--with-X] [--with-vnc] [--profile PROFILE] [--features FEATURES [FEATURES ...]] 90 | [--docker-image DOCKER_IMAGE] [--docker-command DOCKER_COMMAND] [--pull] 91 | [--show-options] [--ccache CCACHE_DIR] [--batch] 92 | 93 | A dockerized approach to test a Gentoo package within a clean stage3. 94 | 95 | options: 96 | -h, --help show this help message and exit 97 | --version show program's version number and exit 98 | --atom ATOM [ATOM ...] 99 | The package atom(s) to install 100 | --binhost BINHOST Binhost URI 101 | --live-ebuild Unmask the live ebuild of the atom 102 | --manual Install package manually 103 | --portage-dir PORTAGE_DIR 104 | The local portage directory 105 | --overlay-dir OVERLAY_DIR 106 | Add overlay dir (can be used multiple times) 107 | --update Update container before installing atom 108 | --install-basic-packages 109 | Install basic packages after container starts 110 | --threads N Use N (default 8) threads to build packages 111 | --use USE [USE ...] The use flags for the atom 112 | --global-use GLOBAL_USE [GLOBAL_USE ...] 113 | Set global USE flag 114 | --unmask ATOM Unmask atom (can be used multiple times) 115 | --unstable Globally 'unstable' system, i.e. ~amd64 116 | --gcc-version VER Use gcc version VER 117 | --python-single-target PYTHON_SINGLE_TARGET 118 | Specify a PYTHON_SINGLE_TARGET 119 | --python-targets PYTHON_TARGETS 120 | Specify a PYTHON_TARGETS 121 | --rm Remove container after session is done 122 | --storage-opt STORAGE_OPT [STORAGE_OPT ...] 123 | Storage driver options for all volumes (same as Docker param) 124 | --with-X Globally enable the X USE flag 125 | --with-vnc Install VNC server to test graphical applications 126 | --profile PROFILE The profile to use (default = default/linux/amd64/23.0) 127 | --features FEATURES [FEATURES ...] 128 | Set FEATURES, see https://wiki.gentoo.org/wiki/FEATURES (default = ['-sandbox', 129 | '-usersandbox', 'userfetch']) 130 | --docker-image DOCKER_IMAGE 131 | Specify the docker image to use (default = gentoo/stage3) 132 | --docker-command DOCKER_COMMAND 133 | Specify the docker command 134 | --pull Download latest docker image 135 | --show-options Show currently selected options and defaults 136 | --ccache CCACHE_DIR Path to mount that contains ccache cache 137 | --batch Do not drop into interactive shell 138 | -------------------------------------------------------------------------------- /docker-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # make sure pre-req interfaces are connected 4 | if ! snapctl is-connected docker-executables; then 5 | echo "the docker-executables content interface must be connected first!" 6 | echo "please run \"snap connect $SNAP_NAME:docker-executables docker:docker-executables\"" 7 | exit 1 8 | fi 9 | 10 | if ! snapctl is-connected docker; then 11 | echo "the docker socket interface must be connected first!" 12 | echo "please run \"snap connect $SNAP_NAME:docker docker:docker-daemon\"" 13 | exit 1 14 | fi 15 | 16 | # docker executables will be in $SNAP/docker-snap after the content interface is 17 | # connected 18 | $SNAP/docker-snap/bin/docker "$@" 19 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = ebuildtester 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ebuildtester documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Feb 9 17:04:39 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.coverage', 37 | 'sphinx.ext.viewcode'] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # 45 | # source_suffix = ['.rst', '.md'] 46 | source_suffix = '.rst' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | from pkg_resources import get_distribution 52 | 53 | # General information about the project. 54 | project = 'ebuildtester' 55 | copyright = '2018-2022, Nicolas Bock' 56 | author = 'Nicolas Bock' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The full version, including alpha/beta/rc tags. 63 | release = get_distribution("ebuildtester").version 64 | # The short X.Y version. 65 | version = release 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = 'en' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This patterns also effect to html_static_path and html_extra_path 77 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 78 | 79 | # The name of the Pygments (syntax highlighting) style to use. 80 | pygments_style = 'sphinx' 81 | 82 | # If true, `todo` and `todoList` produce output, else they produce nothing. 83 | todo_include_todos = False 84 | 85 | 86 | # -- Options for HTML output ---------------------------------------------- 87 | 88 | # The theme to use for HTML and HTML Help pages. See the documentation for 89 | # a list of builtin themes. 90 | # 91 | html_theme = 'sphinx_rtd_theme' 92 | 93 | # Theme options are theme-specific and customize the look and feel of a theme 94 | # further. For a list of options available for each theme, see the 95 | # documentation. 96 | # 97 | # html_theme_options = {} 98 | 99 | # Add any paths that contain custom static files (such as style sheets) here, 100 | # relative to this directory. They are copied after the builtin static files, 101 | # so a file named "default.css" will overwrite the builtin "default.css". 102 | html_static_path = ['_static'] 103 | 104 | # Custom sidebar templates, must be a dictionary that maps document names 105 | # to template names. 106 | # 107 | # This is required for the alabaster theme 108 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 109 | html_sidebars = { 110 | '**': [ 111 | 'about.html', 112 | 'navigation.html', 113 | 'relations.html', # needs 'show_related': True theme option to display 114 | 'searchbox.html', 115 | 'donate.html', 116 | ] 117 | } 118 | 119 | 120 | # -- Options for HTMLHelp output ------------------------------------------ 121 | 122 | # Output file base name for HTML help builder. 123 | htmlhelp_basename = 'ebuildtesterdoc' 124 | 125 | 126 | # -- Options for LaTeX output --------------------------------------------- 127 | 128 | latex_elements = { 129 | # The paper size ('letterpaper' or 'a4paper'). 130 | # 131 | # 'papersize': 'letterpaper', 132 | 133 | # The font size ('10pt', '11pt' or '12pt'). 134 | # 135 | # 'pointsize': '10pt', 136 | 137 | # Additional stuff for the LaTeX preamble. 138 | # 139 | # 'preamble': '', 140 | 141 | # Latex figure (float) alignment 142 | # 143 | # 'figure_align': 'htbp', 144 | } 145 | 146 | # Grouping the document tree into LaTeX files. List of tuples 147 | # (source start file, target name, title, 148 | # author, documentclass [howto, manual, or own class]). 149 | latex_documents = [ 150 | (master_doc, 'ebuildtester.tex', 'ebuildtester Documentation', 151 | 'Nicolas Bock', 'manual'), 152 | ] 153 | 154 | 155 | # -- Options for manual page output --------------------------------------- 156 | 157 | # One entry per manual page. List of tuples 158 | # (source start file, name, description, authors, manual section). 159 | man_pages = [ 160 | (master_doc, 'ebuildtester', 'ebuildtester Documentation', 161 | [author], 1) 162 | ] 163 | 164 | 165 | # -- Options for Texinfo output ------------------------------------------- 166 | 167 | # Grouping the document tree into Texinfo files. List of tuples 168 | # (source start file, target name, title, author, 169 | # dir menu entry, description, category) 170 | texinfo_documents = [ 171 | (master_doc, 'ebuildtester', 'ebuildtester Documentation', 172 | author, 'ebuildtester', 'One line description of project.', 173 | 'Miscellaneous'), 174 | ] 175 | 176 | 177 | # -- Options for Epub output ---------------------------------------------- 178 | 179 | # Bibliographic Dublin Core info. 180 | epub_title = project 181 | epub_author = author 182 | epub_publisher = author 183 | epub_copyright = copyright 184 | 185 | # The unique identifier of the text. This can be a ISBN number 186 | # or the project homepage. 187 | # 188 | # epub_identifier = '' 189 | 190 | # A unique identification for the text. 191 | # 192 | # epub_uid = '' 193 | 194 | # A list of files that should not be packed into the epub file. 195 | epub_exclude_files = ['search.html'] 196 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :caption: Contents: 4 | 5 | .. include:: ../CONTRIBUTING.rst 6 | -------------------------------------------------------------------------------- /docs/ebuildtester.rst: -------------------------------------------------------------------------------- 1 | ebuildtester package 2 | ==================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | ebuildtester.atom module 8 | ------------------------ 9 | 10 | .. automodule:: ebuildtester.atom 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | ebuildtester.docker module 16 | -------------------------- 17 | 18 | .. automodule:: ebuildtester.docker 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | ebuildtester.main module 24 | ------------------------ 25 | 26 | .. automodule:: ebuildtester.main 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | ebuildtester.options module 32 | --------------------------- 33 | 34 | .. automodule:: ebuildtester.options 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | ebuildtester.parse module 40 | ------------------------- 41 | 42 | .. automodule:: ebuildtester.parse 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | ebuildtester.utils module 48 | ------------------------- 49 | 50 | .. automodule:: ebuildtester.utils 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | Module contents 56 | --------------- 57 | 58 | .. automodule:: ebuildtester 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | :numbered: 4 | 5 | ../README.rst 6 | contributing.rst 7 | ebuildtester.rst 8 | 9 | .. include:: ../README.rst 10 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | ebuildtester 2 | ============ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | ebuildtester 8 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | -------------------------------------------------------------------------------- /ebuildtester-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | 5 | ebuildtester --docker-command "flatpak-spawn --host docker" "$@" 6 | -------------------------------------------------------------------------------- /ebuildtester.bash-completion: -------------------------------------------------------------------------------- 1 | # vim: syntax=sh:tabstop=4:shiftwidth=4:expandtab 2 | 3 | _ebuildtester() { 4 | local cur prev opts prefix 5 | COMPREPLY=() 6 | cur="${COMP_WORDS[COMP_CWORD]}" 7 | prev="${COMP_WORDS[COMP_CWORD-1]}" 8 | opts=( 9 | --help 10 | --version 11 | --atom 12 | --binhost 13 | --live-ebuild 14 | --manual 15 | --portage-dir 16 | --overlay-dir 17 | --update 18 | --install-basic-packages 19 | --threads 20 | --use 21 | --global-use 22 | --unmask 23 | --unstable 24 | --gcc-version 25 | --python-single-target 26 | --python-targets 27 | --rm 28 | --storage-opt 29 | --with-X 30 | --with-vnc 31 | --profile 32 | --features 33 | --docker-image 34 | --docker-command 35 | --pull 36 | --show-options 37 | --ccache 38 | --batch 39 | --debug 40 | ) 41 | 42 | case "${prev}" in 43 | --portage-dir|--overlay-dir|--ccache) 44 | COMPREPLY=( $(compgen -o filenames -o dirnames -o plusdirs ${cur}) ) 45 | compopt -o nospace -o filenames -o dirnames -o plusdirs 46 | ;; 47 | --features) 48 | if [[ ${cur} =~ ^- ]]; then 49 | prefix=("-P" "-") 50 | else 51 | prefix=() 52 | fi 53 | echo 54 | COMPREPLY=( $(compgen ${prefix[@]} -W "ccache sandbox userfetch" -- ${cur#-}) ) 55 | ;; 56 | *) 57 | if [[ ${cur} =~ ^-.* || ${COMP_CWORD} -eq 1 ]] ; then 58 | COMPREPLY=( $(compgen -W "${opts[*]}" -- ${cur}) ) 59 | else 60 | echo "I should not be here" 61 | exit 1 62 | fi 63 | ;; 64 | esac 65 | } 66 | 67 | complete -F _ebuildtester ebuildtester 68 | -------------------------------------------------------------------------------- /ebuildtester/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolasbock/ebuildtester/447322fcd10b641a1a0f6259bf82bc8218560e44/ebuildtester/__init__.py -------------------------------------------------------------------------------- /ebuildtester/atom.py: -------------------------------------------------------------------------------- 1 | """An Atom.""" 2 | 3 | 4 | class AtomException(Exception): 5 | """An exception in this class.""" 6 | pass 7 | 8 | 9 | class Atom(object): 10 | 11 | def __init__(self, atom): 12 | # We expect an atom of the form [=]CATEGORY/PACKAGE[-VERSION]. 13 | self.category = None 14 | self.package = None 15 | self.version = None 16 | 17 | # We don't store the optional '='. 18 | temp = atom.split("=") 19 | self.atom = temp[-1] 20 | 21 | try: 22 | self.category, self.package = self.atom.split("/") 23 | except ValueError: 24 | raise AtomException( 25 | "ATOM has to be of the form [=]SECTION/PACKAGE[-VERSION]") 26 | 27 | # Split off version. 28 | try: 29 | temp = self.package.index("-") 30 | if temp > -1: 31 | self.version = self.package[temp + 1:] 32 | self.package = self.package[:temp] 33 | except ValueError: 34 | pass 35 | 36 | def __str__(self): 37 | if self.version is not None: 38 | prefix = "=" 39 | suffix = "-" + self.version 40 | else: 41 | prefix = "" 42 | suffix = "" 43 | return prefix + self.category + "/" + self.package + suffix 44 | 45 | def __eq__(self, other): 46 | result = (self.atom == other.atom) 47 | return result 48 | 49 | def __repr__(self): 50 | return "Atom(\"%s\")" % self.__str__() 51 | -------------------------------------------------------------------------------- /ebuildtester/docker.py: -------------------------------------------------------------------------------- 1 | """The Docker backend.""" 2 | 3 | import os 4 | import re 5 | import sys 6 | import subprocess 7 | 8 | import ebuildtester.options as options 9 | from ebuildtester.utils import massage_string 10 | 11 | 12 | class ExecuteFailure(Exception): 13 | """Failure to execute command.""" 14 | pass 15 | 16 | 17 | class Docker: 18 | """The Docker class.""" 19 | def __init__(self, local_portage, overlay_dirs): 20 | """Create a new container.""" 21 | 22 | docker_image = options.OPTIONS.docker_image 23 | repo_names = self._get_repo_names(overlay_dirs) 24 | overlay_mountpoints = [os.path.join("/var/lib/overlays", r) 25 | for r in repo_names] 26 | 27 | self._setup_container(docker_image) 28 | self._create_container(docker_image, local_portage, 29 | zip(overlay_dirs, overlay_mountpoints)) 30 | self._start_container() 31 | self._set_profile() 32 | self._enable_global_use() 33 | self._tweak_settings() 34 | self._enable_overlays(repo_names) 35 | self._enable_test() 36 | self._unmask_atom() 37 | self._unmask() 38 | self._update() 39 | self._install_basics() 40 | self._set_gcc() 41 | self._print_summary() 42 | 43 | def execute(self, cmd): 44 | """Execute command in container. 45 | 46 | cmd is a list of strings or a simple string with a command that is 47 | executed within a bash shell. 48 | """ 49 | 50 | if isinstance(cmd, list): 51 | cmd_string = ' '.join(cmd) 52 | else: 53 | cmd_string = cmd 54 | options.log.info("%s %s", self.cid[:6], cmd) 55 | docker_cmd = options.OPTIONS.docker_command + ["exec", "--interactive"] 56 | docker_cmd += [self.cid, "/bin/bash"] 57 | docker = subprocess.Popen(docker_cmd, 58 | stdout=subprocess.PIPE, 59 | stderr=subprocess.PIPE, 60 | stdin=subprocess.PIPE, 61 | universal_newlines=True) 62 | docker.stdin.write(cmd_string + "\n") 63 | docker.stdin.close() 64 | 65 | stdout_reader = os.fork() 66 | if stdout_reader == 0: 67 | try: 68 | self._reader(docker, docker.stdout, "stdout") 69 | except KeyboardInterrupt: 70 | pass 71 | sys.exit(0) 72 | 73 | stderr_reader = os.fork() 74 | if stderr_reader == 0: 75 | try: 76 | self._reader(docker, docker.stderr, "stderr") 77 | except KeyboardInterrupt: 78 | pass 79 | sys.exit(0) 80 | 81 | try: 82 | os.waitid(os.P_PID, stdout_reader, os.WEXITED) 83 | os.waitid(os.P_PID, stderr_reader, os.WEXITED) 84 | docker.wait() 85 | except KeyboardInterrupt: 86 | try: 87 | options.log.info("received keyboard interrupt") 88 | docker.terminate() 89 | self.shell() 90 | options.log.info("return from shell, initiating shutdown") 91 | self.cleanup() 92 | sys.exit(0) 93 | except OSError: 94 | pass 95 | docker.wait() 96 | 97 | if docker.returncode != 0: 98 | options.log.error("running in container %s" % (str(self.cid))) 99 | options.log.error("failed command \"%s\"" % (cmd)) 100 | 101 | def shell(self): 102 | """Run an interactive shell in container.""" 103 | 104 | options.log.info("running interactive shell in container") 105 | docker = subprocess.Popen(options.OPTIONS.docker_command 106 | + ["exec", "--tty", "--interactive", 107 | self.cid, "/bin/bash"]) 108 | try: 109 | docker.wait() 110 | except KeyboardInterrupt: 111 | options.log.info("ignoring keyboard interrupt") 112 | 113 | def cleanup(self): 114 | """Clean up.""" 115 | 116 | if options.OPTIONS.rm: 117 | self.remove() 118 | 119 | def remove(self): 120 | """Remove the docker container.""" 121 | 122 | options.log.info("stopping container") 123 | docker = subprocess.Popen(options.OPTIONS.docker_command 124 | + ["kill", self.cid]) 125 | docker.wait() 126 | options.log.info("deleting container") 127 | docker = subprocess.Popen(options.OPTIONS.docker_command 128 | + ["rm", self.cid]) 129 | docker.wait() 130 | 131 | def _reader(self, proc, stream, name): 132 | """Read from a subprocess stream.""" 133 | 134 | while True: 135 | out = stream.readline() 136 | if out == "" and proc.poll() is not None: 137 | break 138 | options.log.info("%s (%s): %s" % 139 | (self.cid[:6], name, out.rstrip())) 140 | options._log_ch.flush() 141 | 142 | def _setup_container(self, docker_image): 143 | """Setup the container.""" 144 | 145 | if options.OPTIONS.pull: 146 | docker_args = options.OPTIONS.docker_command \ 147 | + ["pull", docker_image] 148 | docker = subprocess.Popen(docker_args) 149 | docker.wait() 150 | 151 | def _create_container(self, docker_image, local_portage, overlays): 152 | """Create new container.""" 153 | 154 | docker_args = options.OPTIONS.docker_command \ 155 | + ["create", 156 | "--tty", 157 | "--cap-add", "CAP_SYS_ADMIN", 158 | "--cap-add", "CAP_MKNOD", 159 | "--cap-add", "CAP_NET_ADMIN", 160 | # https://github.com/moby/moby/issues/16429 161 | "--security-opt", "apparmor:unconfined", 162 | "--device", "/dev/fuse", 163 | "--workdir", "/root", 164 | "--volume", "%s:/var/db/repos/gentoo" % local_portage, 165 | "--volume", "%s/distfiles:/var/cache/distfiles" % local_portage, 166 | "--volume", "%s/packages:/var/cache/binpkgs" % local_portage] 167 | 168 | if options.OPTIONS.storage_opt: 169 | for s in options.OPTIONS.storage_opt: 170 | docker_args += ["--storage-opt", "%s" % s] 171 | 172 | ccache = options.OPTIONS.ccache 173 | if ccache: 174 | docker_args += ["--volume=%s:/var/tmp/ccache" % ccache] 175 | 176 | for o in overlays: 177 | docker_args += ["--volume=%s:%s" % o] 178 | 179 | docker_args += [docker_image] 180 | options.log.info("creating docker container with: %s" % 181 | " ".join(docker_args)) 182 | docker = subprocess.Popen(docker_args, stdout=subprocess.PIPE) 183 | docker.wait() 184 | 185 | if docker.returncode != 0: 186 | raise Exception("failure creating docker container") 187 | 188 | lines = docker.stdout.readlines() 189 | if len(lines) > 1: 190 | raise Exception("more output than expected") 191 | self.cid = massage_string(lines[0]).strip() 192 | options.log.info("container id %s" % (self.cid)) 193 | 194 | def _start_container(self): 195 | """Start the container.""" 196 | 197 | docker_args = options.OPTIONS.docker_command \ 198 | + ["start", "%s" % self.cid] 199 | docker = subprocess.Popen(docker_args, stdout=subprocess.PIPE) 200 | docker.wait() 201 | if docker.returncode != 0: 202 | raise Exception("failure creating docker container") 203 | 204 | def _set_profile(self): 205 | """Set the Gentoo profile.""" 206 | 207 | options.log.info("setting Gentoo profile to %s" % 208 | options.OPTIONS.profile) 209 | self.execute("eselect profile set %s" % options.OPTIONS.profile) 210 | 211 | def _tweak_settings(self): 212 | """Tweak portage settings.""" 213 | 214 | options.log.info("tweaking portage settings") 215 | 216 | # Disable the usersandbox feature, it's not working well inside a 217 | # docker container. 218 | self.execute( 219 | f"echo FEATURES=\\\"{' '.join(options.OPTIONS.features)}\\\" " 220 | ">> /etc/portage/make.conf") 221 | 222 | self.execute(("echo MAKEOPTS=\\\"-j%d\\\" " % 223 | (options.OPTIONS.threads)) + 224 | ">> /etc/portage/make.conf") 225 | if options.OPTIONS.binhost: 226 | self.execute("getuto") 227 | self.execute("echo -e '[ebuildtester]\n" 228 | "priority = 999\n" 229 | "sync-uri = {}\n'" 230 | ">> /etc/portage/binrepos.conf/ebuildtester.conf" 231 | .format(options.OPTIONS.binhost)) 232 | if options.OPTIONS.unstable: 233 | self.execute("echo ACCEPT_KEYWORDS=\\\"~amd64\\\" " + 234 | ">> /etc/portage/make.conf") 235 | if options.OPTIONS.with_X: 236 | self.execute("emerge --verbose gentoolkit") 237 | self.execute("euse --enable X") 238 | if options.OPTIONS.python_single_target: 239 | self.execute(("echo */* PYTHON_SINGLE_TARGET: %s" % 240 | (options.OPTIONS.python_single_target)) + 241 | " >> /etc/portage/package.use/python") 242 | if options.OPTIONS.python_targets: 243 | self.execute(("echo */* PYTHON_TARGETS: %s" % 244 | (options.OPTIONS.python_targets)) + 245 | " >> /etc/portage/package.use/python") 246 | 247 | # Avoid wasting time generating the whole set 248 | self.execute('echo "C.UTF-8 UTF-8" > /etc/locale.gen') 249 | 250 | def _get_repo_names(self, overlay_dirs): 251 | """Get repo names from local overlay settings.""" 252 | 253 | repo_names = [] 254 | for o in overlay_dirs: 255 | with open(os.path.join(o, "profiles/repo_name"), "r") as f: 256 | for repo_name in f: 257 | repo_names.append(repo_name.replace("\n", "")) 258 | 259 | return repo_names 260 | 261 | def _enable_overlays(self, repo_names): 262 | """Enable overlays.""" 263 | 264 | for r in repo_names: 265 | self.execute( 266 | "mkdir -p /etc/portage/repos.conf && " + 267 | "echo -e \"[%s]\\n" % str(r) + 268 | "location = /var/lib/overlays/%s\\n" % str(r) + 269 | "master = gentoo\" >> /etc/portage/repos.conf/overlays.conf" 270 | ) 271 | 272 | def _enable_test(self): 273 | """Enable test FEATURES for ATOM.""" 274 | 275 | if options.OPTIONS.atom is not None: 276 | options.log.info("enabling test feature for %s" % 277 | options.OPTIONS.atom) 278 | self.execute("mkdir -p /etc/portage/env") 279 | for a in options.OPTIONS.atom: 280 | self.execute( 281 | "echo \"%s tester.conf\" >> /etc/portage/package.env" % a) 282 | self.execute( 283 | "echo \"FEATURES=\\\"test splitdebug\\\"\" " + 284 | "> /etc/portage/env/tester.conf") 285 | else: 286 | options.log.info("enabling tests skipped, no atoms specified") 287 | 288 | def _unmask_atom(self): 289 | """Unmask the atom to install.""" 290 | 291 | if options.OPTIONS.atom is not None: 292 | options.log.info("unmasking %s" % options.OPTIONS.atom) 293 | for a in options.OPTIONS.atom: 294 | if options.OPTIONS.live_ebuild: 295 | unmask_keyword = "**" 296 | else: 297 | unmask_keyword = "~amd64" 298 | self.execute("mkdir -p /etc/portage/package.accept_keywords") 299 | self.execute( 300 | "echo \"" + str(a) + "\" " + unmask_keyword + " >> " + 301 | "/etc/portage/package.accept_keywords/testbuild") 302 | if len(options.OPTIONS.use) > 0: 303 | for a in options.OPTIONS.atom: 304 | self.execute( 305 | "echo %s %s >> /etc/portage/package.use/testbuild" % 306 | (str(a), " ".join(options.OPTIONS.use))) 307 | else: 308 | options.log.info("no atoms to unmask") 309 | 310 | def _unmask(self): 311 | """Unmask other atoms.""" 312 | 313 | options.log.info("unmasking additional atoms") 314 | for a in options.OPTIONS.unmask: 315 | options.log.info(" unmasking %s" % a) 316 | self.execute("mkdir -p /etc/portage/package.accept_keywords") 317 | self.execute( 318 | "echo \"%s\" ~amd64 >> " 319 | "/etc/portage/package.accept_keywords/testbuild" % a) 320 | 321 | def _update(self): 322 | """Update container.""" 323 | 324 | if not options.OPTIONS.update: 325 | options.log.info("skipping update") 326 | else: 327 | options.log.info("updating container") 328 | update_options = ["--verbose", "--update", 329 | "--deep", "--newuse", "--changed-deps"] 330 | self.execute("emerge " + " ".join(update_options) + " @world") 331 | 332 | def _install_basics(self): 333 | """Install some basic packages.""" 334 | 335 | if options.OPTIONS.ccache: 336 | self.execute("emerge --verbose dev-util/ccache") 337 | 338 | if not options.OPTIONS.install_basic_packages: 339 | options.log.info("skipping basic packages") 340 | else: 341 | options.log.info("installing basic packages: %s" % 342 | options.base_packages) 343 | self.execute("emerge --verbose %s" % 344 | " ".join(map(str, options.base_packages))) 345 | 346 | if options.OPTIONS.with_vnc: 347 | t = ["net-misc/tigervnc", "x11-wm/icewm"] 348 | options.log.info("installing VNC packages: %s" % t) 349 | self.execute("emerge --verbose %s" % " ".join(map(str, t))) 350 | 351 | def _enable_global_use(self): 352 | """Enable global USE settings.""" 353 | if not options.OPTIONS.global_use: 354 | options.log.info("no global USE flags given, skipping") 355 | else: 356 | options.log.info("setting global USE flags") 357 | self.execute("emerge --verbose gentoolkit") 358 | for u in options.OPTIONS.global_use: 359 | self.execute("euse --enable %s" % u) 360 | 361 | def _set_gcc(self): 362 | """Set gcc in the container.""" 363 | 364 | options.log.info("setting gcc") 365 | if options.OPTIONS.gcc_version: 366 | self.execute("mkdir -p /etc/portage/package.accept_keywords") 367 | self.execute( 368 | ("echo =sys-devel/gcc-%s ** >> " % 369 | options.OPTIONS.gcc_version) + 370 | "/etc/portage/package.accept_keywords/testbuild") 371 | self.execute("emerge --verbose sys-devel/gcc") 372 | gcc = re.sub("-r[0-9]+$", "", options.OPTIONS.gcc_version) 373 | self.execute("gcc-config $(gcc-config --list-profiles | " + 374 | ("grep %s | " % gcc) + 375 | "sed -e 's:^.*\\[\\([0-9]\\+\\)\\].*:\\1:')") 376 | self.execute("emerge --verbose --oneshot sys-devel/libtool") 377 | 378 | def _print_summary(self): 379 | """Print summary.""" 380 | 381 | options.log.info("summary") 382 | self.execute( 383 | "if [[ -d /etc/portage/package.accept_keywords ]]; then " + 384 | "cat /etc/portage/package.accept_keywords/*; fi") 385 | self.execute("if [[ -f /etc/portage/package.use/testbuild ]]; then " + 386 | "cat /etc/portage/package.use/testbuild; fi") 387 | self.execute("emerge --info") 388 | self.execute("qlop") 389 | -------------------------------------------------------------------------------- /ebuildtester/main.py: -------------------------------------------------------------------------------- 1 | """The main function.""" 2 | 3 | import os.path 4 | import sys 5 | 6 | from ebuildtester.atom import Atom 7 | from ebuildtester.docker import Docker, ExecuteFailure 8 | from ebuildtester.parse import parse_commandline 9 | import ebuildtester.options as options 10 | 11 | 12 | def main(): 13 | """The main function.""" 14 | 15 | options.OPTIONS = parse_commandline(sys.argv[1:]) 16 | if len(options.OPTIONS.atom) > 0: 17 | options.set_logfile('ebuildtester-' 18 | + ':'.join([f'{atom.category}-{atom.package}' 19 | for atom in options.OPTIONS.atom]) 20 | + '.log') 21 | else: 22 | options.set_logfile('ebuildtester-manual.log') 23 | 24 | if options.OPTIONS.debug: 25 | options.set_debugLoglevel() 26 | 27 | options.log.info( 28 | "*** please note that all necessary licenses will be accepted ***") 29 | options.log.info("creating container") 30 | container = Docker( 31 | os.path.abspath(os.path.expanduser(options.OPTIONS.portage_dir)), 32 | [os.path.abspath(p) for p in options.OPTIONS.overlay_dir]) 33 | 34 | options.log.info("created container %s", container.cid) 35 | if options.OPTIONS.manual: 36 | container.shell() 37 | else: 38 | emerge_command = [ 39 | "emerge", 40 | "--verbose ", 41 | "--autounmask-write=y ", 42 | "--autounmask-license=y ", 43 | "--autounmask-continue=y "] 44 | 45 | atom = Atom(" ".join(map(str, options.OPTIONS.atom))) 46 | 47 | if options.OPTIONS.binhost: 48 | p = "{}/{}".format(atom.category, atom.package) 49 | emerge_command.append("--usepkg-exclude={}".format(p)) 50 | 51 | emerge_command.append(str(atom)) 52 | container.execute(" ".join(["echo"] + emerge_command + ["--ask"]) + 53 | " >> ~/.bash_history") 54 | 55 | for i in range(5): 56 | options.log.info("emerge attempt %d (of %d)", i + 1, 5) 57 | try: 58 | container.execute(emerge_command) 59 | except ExecuteFailure: 60 | options.log.warning( 61 | "command failed, updating configuration files") 62 | container.execute("etc-update --verbose --automode -5") 63 | else: 64 | break 65 | if not options.OPTIONS.batch: 66 | options.log.info("opening interactive shell") 67 | container.shell() 68 | 69 | container.cleanup() 70 | -------------------------------------------------------------------------------- /ebuildtester/options.py: -------------------------------------------------------------------------------- 1 | """Options and some initializations.""" 2 | 3 | import logging 4 | import os 5 | 6 | from ebuildtester.atom import Atom 7 | 8 | base_packages = list(map(Atom, ["app-portage/gentoolkit", 9 | "app-portage/flaggie"])) 10 | OPTIONS = None 11 | log = logging.getLogger("test-package") 12 | _log_default_formatter = \ 13 | logging.Formatter("%(asctime)s - %(message)s") 14 | _log_debug_formatter = \ 15 | logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") 16 | _log_ch = logging.StreamHandler() 17 | _log_ch.setLevel(logging.INFO) 18 | _log_ch.setFormatter(_log_default_formatter) 19 | _log_fh = None 20 | log.addHandler(_log_ch) 21 | log.setLevel(logging.DEBUG) 22 | 23 | _logdir = os.getenv('XDG_STATE_HOME', '/tmp') 24 | _logfile = None 25 | _log_filehandle = None 26 | 27 | 28 | def set_logfile(logfile): 29 | """Add a logfile to logging.""" 30 | 31 | global _log_fh 32 | 33 | _logfile = os.path.join(_logdir, logfile) 34 | _log_fh = logging.FileHandler(_logfile, "a") 35 | _log_fh.setLevel(logging.INFO) 36 | _log_fh.setFormatter(_log_default_formatter) 37 | log.addHandler(_log_fh) 38 | log.info("logging at %s", _logfile) 39 | 40 | 41 | def set_debugLoglevel(): 42 | """Set logging level.""" 43 | _log_ch.setLevel(logging.DEBUG) 44 | _log_ch.setFormatter(_log_debug_formatter) 45 | _log_fh.setLevel(logging.DEBUG) 46 | _log_fh.setFormatter(_log_debug_formatter) 47 | -------------------------------------------------------------------------------- /ebuildtester/parse.py: -------------------------------------------------------------------------------- 1 | """Parse command line options.""" 2 | 3 | import os 4 | import argparse 5 | import multiprocessing 6 | from importlib.metadata import version 7 | 8 | from ebuildtester.atom import Atom 9 | 10 | 11 | def parse_commandline(args): 12 | """Parse the command line.""" 13 | 14 | parser = argparse.ArgumentParser( 15 | description="A dockerized approach to test a Gentoo " 16 | "package within a clean stage3.") 17 | parser.add_argument( 18 | "--version", 19 | action="version", 20 | version="v" + version("ebuildtester")) 21 | parser.add_argument( 22 | "--atom", 23 | help="The package atom(s) to install", 24 | nargs="+", 25 | action="append") 26 | parser.add_argument( 27 | "--binhost", 28 | help="Binhost URI") 29 | parser.add_argument( 30 | "--live-ebuild", 31 | help="Unmask the live ebuild of the atom", 32 | action="store_true") 33 | parser.add_argument( 34 | "--manual", 35 | help="Install package manually", 36 | default=False, 37 | action="store_true") 38 | parser.add_argument( 39 | "--portage-dir", 40 | help="The local portage directory", 41 | required=True) 42 | parser.add_argument( 43 | "--overlay-dir", 44 | help="Add overlay dir (can be used multiple times)", 45 | action="append", 46 | default=[]) 47 | parser.add_argument( 48 | "--update", 49 | help="Update container before installing atom", 50 | action="store_true") 51 | parser.add_argument( 52 | "--install-basic-packages", 53 | help="Install basic packages after container starts", 54 | action="store_true") 55 | parser.add_argument( 56 | "--threads", 57 | metavar="N", 58 | help="Use N (default %(default)s) threads to build packages", 59 | default=multiprocessing.cpu_count(), 60 | type=int) 61 | parser.add_argument( 62 | "--use", 63 | help="The use flags for the atom", 64 | default=[], 65 | nargs="+") 66 | parser.add_argument( 67 | "--global-use", 68 | help="Set global USE flag", 69 | default=[], 70 | nargs="+") 71 | parser.add_argument( 72 | "--unmask", 73 | metavar="ATOM", 74 | help="Unmask atom (can be used multiple times)", 75 | default=[], 76 | action="append") 77 | parser.add_argument( 78 | "--unstable", 79 | help="Globally 'unstable' system, i.e. ~amd64", 80 | action="store_true") 81 | parser.add_argument( 82 | "--gcc-version", 83 | metavar="VER", 84 | help="Use gcc version VER") 85 | parser.add_argument( 86 | "--python-single-target", 87 | metavar="PYTHON_SINGLE_TARGET", 88 | help="Specify a PYTHON_SINGLE_TARGET") 89 | parser.add_argument( 90 | "--python-targets", 91 | metavar="PYTHON_TARGETS", 92 | help="Specify a PYTHON_TARGETS") 93 | parser.add_argument( 94 | "--rm", 95 | help="Remove container after session is done", 96 | action="store_true") 97 | parser.add_argument( 98 | "--storage-opt", 99 | help="Storage driver options for all volumes (same as Docker param)", 100 | nargs="+", 101 | action="append") 102 | parser.add_argument( 103 | "--with-X", 104 | help="Globally enable the X USE flag", 105 | action="store_true") 106 | parser.add_argument( 107 | "--with-vnc", 108 | help="Install VNC server to test graphical applications", 109 | action="store_true") 110 | parser.add_argument( 111 | "--profile", 112 | help="The profile to use (default = %(default)s)", 113 | default="default/linux/amd64/23.0") 114 | parser.add_argument( 115 | '--features', 116 | help="Set FEATURES, see https://wiki.gentoo.org/wiki/FEATURES " 117 | "(default = %(default)s)", 118 | default=["-sandbox", "-usersandbox", "userfetch"], 119 | nargs="+", 120 | action="append") 121 | parser.add_argument( 122 | "--docker-image", 123 | help="Specify the docker image to use (default = %(default)s)", 124 | default="gentoo/stage3") 125 | parser.add_argument( 126 | "--docker-command", 127 | help="Specify the docker command") 128 | parser.add_argument( 129 | "--pull", 130 | help="Download latest docker image", 131 | action="store_true") 132 | parser.add_argument( 133 | "--show-options", 134 | help="Show currently selected options and defaults", 135 | action="store_true") 136 | parser.add_argument( 137 | "--ccache", 138 | metavar="CCACHE_DIR", 139 | help="Path to mount that contains ccache cache") 140 | parser.add_argument( 141 | '--batch', 142 | help='Do not drop into interactive shell', 143 | action='store_true') 144 | parser.add_argument( 145 | '--debug', 146 | help='Add some debugging output', 147 | action='store_true') 148 | 149 | if '--complete' in args: 150 | print('Suggesting') 151 | args.pop(args.index('--complete')) 152 | print(f'args = {",".join(args)}') 153 | 154 | options = parser.parse_args(args) 155 | 156 | if not options.manual and options.atom is None: 157 | raise Exception("either specify an atom or use --manual") 158 | 159 | if options.atom: 160 | temp = [] 161 | for atom in options.atom: 162 | temp += atom 163 | options.atom = temp 164 | else: 165 | options.atom = [] 166 | 167 | temp = [] 168 | for feature in options.features: 169 | if type(feature) is list: 170 | temp += feature 171 | else: 172 | temp.append(feature) 173 | options.features = temp 174 | 175 | if options.binhost: 176 | options.features.append("getbinpkg") 177 | 178 | if options.ccache: 179 | options.features.append("ccache") 180 | 181 | options.atom = list(map(Atom, options.atom)) 182 | 183 | if options.update in ["yes", "true"]: 184 | options.update = True 185 | else: 186 | options.update = False 187 | 188 | if not options.docker_command: 189 | options.docker_command = os.getenv('DOCKER_COMMAND', default='docker') 190 | 191 | # Convert docker command into list so that `subprocess` can run the command 192 | # and add command line options if they are present. 193 | options.docker_command = options.docker_command.split() 194 | 195 | if options.show_options: 196 | print(options) 197 | 198 | return options 199 | -------------------------------------------------------------------------------- /ebuildtester/utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def massage_string(string): 5 | """Return a string.""" 6 | 7 | if sys.version_info[0] < 3: 8 | return string 9 | else: 10 | return string.decode("UTF-8") 11 | -------------------------------------------------------------------------------- /io.github.nicolasbock.ebuildtester.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.github.nicolasbock.ebuildtester 5 | CC0-1.0 6 | 7 | ebuildtester 8 | 9 | Tool to test Gentoo ebuilds in a container. 10 | 11 | 12 | This script is a tool to test a Gentoo ebuild and its dependencies. The idea is that the package is emerged in a clean (and current) stage3 Docker container. 13 | 14 | 15 | 16 | 17 |
    18 |
  • First release with FlatPak support
  • 19 |
20 |
21 |
22 |
23 | https://github.com/ebuildtester 24 |
25 | -------------------------------------------------------------------------------- /io.github.nicolasbock.ebuildtester.yaml: -------------------------------------------------------------------------------- 1 | app-id: io.github.nicolasbock.ebuildtester 2 | runtime: org.freedesktop.Platform 3 | runtime-version: '21.08' 4 | sdk: org.freedesktop.Sdk 5 | command: ebuildtester-wrapper 6 | finish-args: 7 | - --talk-name=org.freedesktop.Flatpak 8 | modules: 9 | - python3-modules.json 10 | - name: ebuildtester 11 | buildsystem: simple 12 | build-commands: 13 | - pip3 install --prefix /app --verbose . 14 | - python setup.py install --prefix /app --verbose 15 | - install --verbose -D ebuildtester-wrapper.sh /app/bin/ebuildtester-wrapper 16 | sources: 17 | - type: dir 18 | path: . 19 | -------------------------------------------------------------------------------- /python3-modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "python3-setuptools-scm", 3 | "buildsystem": "simple", 4 | "build-commands": [ 5 | "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"setuptools-scm\" --no-build-isolation" 6 | ], 7 | "sources": [ 8 | { 9 | "type": "file", 10 | "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", 11 | "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" 12 | }, 13 | { 14 | "type": "file", 15 | "url": "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", 16 | "sha256": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 17 | }, 18 | { 19 | "type": "file", 20 | "url": "https://files.pythonhosted.org/packages/e3/e5/c28b544051340e63e0d507eb893c9513d3a300e5e9183e2990518acbfe36/setuptools_scm-6.4.2-py3-none-any.whl", 21 | "sha256": "acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4" 22 | }, 23 | { 24 | "type": "file", 25 | "url": "https://files.pythonhosted.org/packages/d9/41/d9cfb4410589805cd787f8a82cddd13142d9bf7449d12adf2d05a4a7d633/pyparsing-3.0.8-py3-none-any.whl", 26 | "sha256": "ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | with open("README.rst", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setup( 9 | name="ebuildtester", 10 | author="Nicolas Bock", 11 | author_email="nicolasbock@gmail.com", 12 | description="A container approach to test a Gentoo package within a clean stage3 container", 13 | license="BSD", 14 | url="http://ebuildtester.readthedocs.io/", 15 | long_description=long_description, 16 | long_description_content_type="text/x-rst", 17 | use_scm_version=True, 18 | setup_requires=['setuptools_scm'], 19 | packages=["ebuildtester"], 20 | entry_points={ 21 | "console_scripts": [ 22 | "ebuildtester = ebuildtester.main:main" 23 | ] 24 | } 25 | ) 26 | -------------------------------------------------------------------------------- /snap/hooks/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | 5 | if ! snapctl is-connected docker-executables; then 6 | echo "Please run" 7 | echo 8 | echo "snap connect ebuildtester:docker-executables docker:docker-executables" 9 | echo 10 | echo "in order to use the ebuildtester command." 11 | # exit 1 12 | fi 13 | -------------------------------------------------------------------------------- /snap/hooks/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | 5 | echo "Running intall hook" 6 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: ebuildtester 2 | base: core22 3 | adopt-info: ebuildtester 4 | summary: Tool to test a Gentoo ebuild and its dependencies 5 | description: | 6 | This is a tool to test a Gentoo ebuild and its dependencies. The idea is 7 | that the ebuild is emerged in a clean (and current) stage3 Docker container. 8 | 9 | * Installation 10 | 11 | This snap uses the `docker` snap and needs to be connected via 12 | 13 | snap connect ebuildtester:docker-executables docker:docker-executables 14 | 15 | * Usage 16 | 17 | We are going to assume that the user has a local git clone of the portage 18 | tree in 19 | 20 | /usr/local/git/gentoo 21 | 22 | We have added a new ebuild and would like to verify that the build 23 | dependencies are all correct. We can build the package (ATOM) with: 24 | 25 | ebuildtester \ 26 | --portage-dir /usr/local/git/gentoo \ 27 | --atom ATOM \ 28 | --use USE1 USE2 29 | 30 | where we have specified two USE flags, USE1 and USE2. The `ebuildtester` 31 | command will now create a docker container and start installing the ATOM. 32 | All specified dependencies will be installed as well. 33 | 34 | For a full list of supported options run 35 | 36 | ebuildtester --help 37 | 38 | grade: stable 39 | confinement: strict 40 | 41 | environment: 42 | DOCKER_COMMAND: docker-wrapper.sh 43 | 44 | plugs: 45 | docker-executables: 46 | interface: content 47 | target: $SNAP/docker-snap 48 | default-provider: docker 49 | 50 | apps: 51 | ebuildtester: 52 | environment: 53 | XDG_STATE_HOME: ${SNAP_USER_DATA} 54 | command: bin/ebuildtester 55 | completer: usr/share/bash-completion/completions/ebuildtester.bash-completion 56 | plugs: 57 | - home 58 | docker: 59 | command: usr/bin/docker-wrapper.sh 60 | plugs: 61 | - docker 62 | 63 | parts: 64 | ebuildtester: 65 | plugin: python 66 | source: . 67 | source-type: git 68 | source-branch: main 69 | override-pull: | 70 | snapcraftctl pull 71 | snapcraftctl set-version "$(git describe)" 72 | python-requirements: 73 | - requirements.txt 74 | 75 | docker-wrapper: 76 | plugin: dump 77 | source: . 78 | override-build: | 79 | set -e -u -x 80 | install -D docker-wrapper.sh \ 81 | ${SNAPCRAFT_PART_INSTALL}/usr/bin/docker-wrapper.sh 82 | install -D ebuildtester.bash-completion \ 83 | ${SNAPCRAFT_PART_INSTALL}/usr/share/bash-completion/completions/ebuildtester.bash-completion 84 | stage: 85 | - usr/bin/docker-wrapper.sh 86 | - usr/share/bash-completion/completions/ebuildtester.bash-completion 87 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | pycodestyle 2 | bandit 3 | pytest 4 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicolasbock/ebuildtester/447322fcd10b641a1a0f6259bf82bc8218560e44/test/__init__.py -------------------------------------------------------------------------------- /test/test_atom.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from ebuildtester.atom import Atom, AtomException 3 | 4 | 5 | class TestAtom(unittest.TestCase): 6 | 7 | def test_equals(self): 8 | atom = Atom("=CATEGORY/PACKAGE-1.0.0") 9 | self.assertEqual(atom.__str__()[0], "=") 10 | self.assertEqual(atom.__str__(), "=CATEGORY/PACKAGE-1.0.0") 11 | 12 | def test_slash_1(self): 13 | with self.assertRaises(AtomException) as e: 14 | Atom("ATOM") 15 | 16 | def test_slash_2(self): 17 | atom = Atom("CATEGORY/PACKAGE") 18 | self.assertEqual(atom.category, "CATEGORY") 19 | self.assertEqual(atom.package, "PACKAGE") 20 | 21 | def test_version(self): 22 | self.assertEqual(Atom("CATEGORY/PACKAGE-1").version, "1") 23 | self.assertEqual(Atom("CATEGORY/PACKAGE-1.0").version, "1.0") 24 | self.assertEqual( 25 | Atom("CATEGORY/PACKAGE-1.0-r1").version, "1.0-r1") 26 | 27 | def test_str(self): 28 | atom_1 = Atom("=CATEGORY/PACKAGE-1.0.0-r1") 29 | atom_2 = Atom(str(atom_1)) 30 | self.assertEqual(atom_1, atom_2) 31 | -------------------------------------------------------------------------------- /test/test_parse.py: -------------------------------------------------------------------------------- 1 | from ebuildtester.atom import Atom 2 | import ebuildtester.parse 3 | import unittest 4 | 5 | 6 | class TestParse(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.args = ["--portage-dir", "~/gentoo"] 10 | 11 | def test_atom(self): 12 | options = ebuildtester.parse.parse_commandline( 13 | self.args + ["--atom", "=SECTION/ATOM-1.0.0"]) 14 | self.assertTrue(Atom("=SECTION/ATOM-1.0.0") in options.atom) 15 | 16 | def test_binhost(self): 17 | options = ebuildtester.parse.parse_commandline( 18 | self.args + ["--manual", "--binhost", "http://localhost:8080"]) 19 | self.assertIn("http://localhost:8080", options.binhost) 20 | 21 | def test_live_ebuild(self): 22 | options = ebuildtester.parse.parse_commandline( 23 | self.args + ["--manual", "--live-ebuild"]) 24 | self.assertTrue(options.live_ebuild) 25 | 26 | def test_manual(self): 27 | options = ebuildtester.parse.parse_commandline(self.args + ["--manual"]) 28 | self.assertTrue(options.manual) 29 | 30 | def test_portage_dir(self): 31 | import sys 32 | if sys.version_info[0] == 2 and sys.version_info[1] == 6: 33 | self.assertRaises(Exception, ebuildtester.parse.parse_commandline()) 34 | else: 35 | with self.assertRaises(Exception): 36 | options = ebuildtester.parse.parse_commandline() 37 | options = ebuildtester.parse.parse_commandline( 38 | self.args + ["--manual"]) 39 | self.assertEqual("~/gentoo", options.portage_dir) 40 | 41 | def test_overlay_dir(self): 42 | options = ebuildtester.parse.parse_commandline( 43 | self.args + ["--manual", "--overlay-dir", "."]) 44 | self.assertTrue("." in options.overlay_dir) 45 | 46 | def test_update(self): 47 | options = ebuildtester.parse.parse_commandline( 48 | self.args + ["--manual", "--update"]) 49 | self.assertFalse(options.update) 50 | 51 | def test_threads(self): 52 | options = ebuildtester.parse.parse_commandline( 53 | self.args + ["--manual", "--threads", "4"]) 54 | self.assertEqual(options.threads, 4) 55 | 56 | def test_use(self): 57 | options = ebuildtester.parse.parse_commandline( 58 | self.args + ["--manual", "--use", "a", "b", "c"]) 59 | self.assertTrue("a" in options.use) 60 | self.assertTrue("b" in options.use) 61 | self.assertTrue("c" in options.use) 62 | 63 | def test_unmask(self): 64 | options = ebuildtester.parse.parse_commandline( 65 | self.args + ["--manual", "--unmask", "ATOM"]) 66 | self.assertTrue("ATOM" in options.unmask) 67 | 68 | def test_gcc_version(self): 69 | options = ebuildtester.parse.parse_commandline( 70 | self.args + ["--manual", "--gcc-version", "VER"]) 71 | self.assertEqual("VER", options.gcc_version) 72 | 73 | def test_python_single_target(self): 74 | options = ebuildtester.parse.parse_commandline( 75 | self.args + ["--manual", "--python-single-target", "-* python3_8"]) 76 | self.assertEqual("-* python3_8", options.python_single_target) 77 | 78 | def test_python_targets(self): 79 | options = ebuildtester.parse.parse_commandline( 80 | self.args + ["--manual", "--python-targets", "python3_8"]) 81 | self.assertEqual("python3_8", options.python_targets) 82 | 83 | def test_docker_image(self): 84 | options = ebuildtester.parse.parse_commandline( 85 | self.args + ["--manual"]) 86 | self.assertEqual(options.docker_image, "gentoo/stage3") 87 | -------------------------------------------------------------------------------- /test/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import ebuildtester.utils 3 | 4 | 5 | class TestUtils(unittest.TestCase): 6 | 7 | def test_massage_string(self): 8 | pass 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3, bandit, pep8 3 | 4 | [testenv] 5 | deps = -r test-requirements.txt 6 | install_command = 7 | pip install {opts} --upgrade {packages} pip setuptools 8 | commands = 9 | pip list --outdated 10 | python -m unittest --verbose 11 | 12 | [testenv:docs] 13 | deps = -r docs/requirements.txt 14 | commands = 15 | sphinx-apidoc --force --output-dir docs ebuildtester 16 | sphinx-build -M html docs docs/_build 17 | sphinx-build -M doctest docs docs/_build 18 | sphinx-build -M linkcheck docs docs/_build 19 | sphinx-build -M coverage docs docs/_build 20 | sphinx-build -M man docs docs/_build 21 | 22 | [testenv:bandit] 23 | commands = bandit --exit-zero --recursive {toxinidir}/ebuildtester {toxinidir}/test 24 | 25 | [testenv:pep8] 26 | commands = pycodestyle ebuildtester 27 | 28 | [testenv:pypi] 29 | deps = 30 | setuptools 31 | wheel 32 | twine 33 | commands = 34 | python setup.py sdist --dist-dir {envdir}/dist 35 | python setup.py bdist_wheel --dist-dir {envdir}/dist 36 | twine check --strict {envdir}/dist/* 37 | --------------------------------------------------------------------------------