├── .python-version ├── src └── xcp_ng_dev │ ├── __init__.py │ └── cli.py ├── .gitignore ├── requirements ├── base.txt ├── dev.txt └── update_requirements.py ├── container ├── files │ ├── Alma10-devel.repo │ ├── xcp-ng-8.99.repo │ ├── entrypoint.sh │ ├── rpmmacros │ ├── CentOS-Vault.repo.in │ ├── yum.conf.xs │ ├── xcp-ng.repo.7.x.in │ ├── init-container.sh │ └── xcp-ng.repo.8.x.in ├── build.sh ├── Dockerfile-9.x └── Dockerfile-8.x ├── .github ├── workflows │ ├── requirements-check.yml │ ├── main.yaml │ ├── zizmor.yml │ ├── code-checkers.yml │ ├── release.yml │ └── docker.yml └── actions │ └── uv-setup │ └── action.yml ├── test └── test.sh ├── LICENSE ├── pyproject.toml ├── README.md └── uv.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.11 2 | -------------------------------------------------------------------------------- /src/xcp_ng_dev/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | build/ 3 | *.egg-info/ 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | # generated with update_requirements.py, do not edit manually 2 | argcomplete 3 | -------------------------------------------------------------------------------- /container/files/Alma10-devel.repo: -------------------------------------------------------------------------------- 1 | [alma10-devel] 2 | name=Almalinux 10 devel 3 | baseurl=https://vault.almalinux.org/10.0/devel/$basearch/os/ 4 | enabled=1 5 | gpgcheck=1 6 | -------------------------------------------------------------------------------- /container/files/xcp-ng-8.99.repo: -------------------------------------------------------------------------------- 1 | [xcpng] 2 | name=XCP-ng 8.99 test 3 | baseurl=http://repos/repos/ydi/v9alma10v2/ 4 | enabled=1 5 | gpgcheck=0 6 | metadata_expire=0 7 | enabled_metadata=1 8 | priority=1 9 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | # generated with update_requirements.py, do not edit manually 2 | icecream 3 | mypy 4 | flake8 5 | pyright 6 | ruff 7 | typing-extensions 8 | flake8-pyproject 9 | -r base.txt 10 | -------------------------------------------------------------------------------- /.github/workflows/requirements-check.yml: -------------------------------------------------------------------------------- 1 | name: Check requirements file consistency 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | requirements-check: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | - uses: ./.github/actions/uv-setup/ 19 | with: 20 | dev: false 21 | - run: ./requirements/update_requirements.py 22 | - run: git diff --exit-code 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Test build env 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: true # required for git lfs 18 | - uses: ./.github/actions/uv-setup/ 19 | - name: Test 20 | # use script to provide a tty (workaround of systematic "docker -t"?) 21 | shell: 'script -q -e -c "bash {0}"' 22 | run: | 23 | ./test/test.sh 24 | -------------------------------------------------------------------------------- /.github/workflows/zizmor.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Security Analysis with zizmor 🌈 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | zizmor: 13 | name: zizmor latest 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | - uses: ./.github/actions/uv-setup/ 20 | with: 21 | sync: false 22 | - run: uvx zizmor --color=always . 23 | env: 24 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | TARGET_XCP_NG_VERSION="8.2" 6 | REPOS=xcp-emu-manager 7 | 8 | REPO_PATH=$(mktemp --directory --tmpdir xcp-buildenv-test.XXXXXX) 9 | trap "rm -rf '$REPO_PATH'" EXIT 10 | 11 | # clone first, to be sure that the token won't have expired 12 | for REPO in ${REPOS}; do 13 | git clone --branch "$TARGET_XCP_NG_VERSION" https://github.com/xcp-ng-rpms/"$REPO" "$REPO_PATH/$REPO" 14 | done 15 | 16 | ./container/build.sh "$TARGET_XCP_NG_VERSION" 17 | 18 | CONTAINER_NAME=${CONTAINER_NAME:-build-env} 19 | 20 | for REPO in ${REPOS}; do 21 | xcp-ng-dev container build "$TARGET_XCP_NG_VERSION" "$REPO_PATH/$REPO" \ 22 | --name "$CONTAINER_NAME" 23 | done 24 | -------------------------------------------------------------------------------- /container/files/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | if [ -n "${SCRIPT_DEBUG}" ]; then 5 | set -x 6 | fi 7 | 8 | if [ "${BUILDER_UID}" ]; then 9 | # BUILDER_UID is defined, update the builder ids, and continue with the builder user 10 | if [ "${BUILDER_GID}" != "1000" ]; then 11 | groupmod -g "${BUILDER_GID}" builder 12 | fi 13 | if [ "${BUILDER_UID}" != "1000" ]; then 14 | usermod -u "${BUILDER_UID}" -g "${BUILDER_GID}" builder 15 | fi 16 | find ~builder -maxdepth 1 -type f | xargs chown builder:builder 17 | # use gosu to switch user to make the command run the root process and properly 18 | # deal with signals 19 | exec /usr/local/bin/gosu builder "$@" 20 | else 21 | # no BUILDER_ID, just continue as the current user 22 | exec "$@" 23 | fi 24 | -------------------------------------------------------------------------------- /container/files/rpmmacros: -------------------------------------------------------------------------------- 1 | # The changes below regarding opamroot are only needed for older XCP-ng releases 2 | # Starting with more recent XCP-ng releases (8.1? 8.2?), xs-opam-repo includes a profile.d 3 | # file that one has to source, which we do when we start the container. 4 | # And by the way opamroot has moved to /usr/lib64 5 | 6 | # taken from /usr/lib/rpm/macros 7 | %__spec_build_pre %{___build_pre}\ 8 | [ ! -d /usr/lib/opamroot ] || eval $(opam config env --root=/usr/lib/opamroot) 9 | 10 | # taken from /usr/lib/rpm/redhat/macros 11 | %__spec_install_pre %{___build_pre}\ 12 | [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "${RPM_BUILD_ROOT}"\ 13 | mkdir -p `dirname "$RPM_BUILD_ROOT"`\ 14 | mkdir "$RPM_BUILD_ROOT"\ 15 | [ ! -d /usr/lib/opamroot ] || eval $(opam config env --root=/usr/lib/opamroot) 16 | 17 | -------------------------------------------------------------------------------- /requirements/update_requirements.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import tomllib 5 | from pathlib import Path 6 | 7 | parser = argparse.ArgumentParser(description="Convert the dependencies from pyproject.toml in requirements.txt files") 8 | args = parser.parse_args() 9 | 10 | PROJECT_DIR = Path(__file__).parent.parent 11 | HEADER = "# generated with update_requirements.py, do not edit manually" 12 | 13 | with open(f'{PROJECT_DIR}/pyproject.toml', 'rb') as f: 14 | pyproject = tomllib.load(f) 15 | 16 | 17 | main_deps = pyproject['project']['dependencies'] 18 | with open(f'{PROJECT_DIR}/requirements/base.txt', 'w') as f: 19 | print(HEADER, file=f) 20 | for dep in main_deps: 21 | print(dep, file=f) 22 | 23 | dev_deps = pyproject['dependency-groups']['dev'] 24 | with open(f'{PROJECT_DIR}/requirements/dev.txt', 'w') as f: 25 | print(HEADER, file=f) 26 | for dep in dev_deps: 27 | print(dep, file=f) 28 | print('-r base.txt', file=f) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 Citrix Systems 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/actions/uv-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup UV and Sync 2 | description: 'Install uv and sync the dependencies' 3 | inputs: 4 | sync: 5 | description: 'Whether to run `uv sync` after setting up.' 6 | required: false 7 | default: 'true' 8 | type: boolean 9 | dev: 10 | description: 'Whether to use `--no-dev` with `uv sync`.' 11 | required: false 12 | default: 'true' 13 | type: boolean 14 | activate-environment: 15 | description: 'Wether to activate the virtual env or not' 16 | required: false 17 | default: true 18 | type: boolean 19 | runs: 20 | using: 'composite' 21 | steps: 22 | - uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1 23 | with: 24 | version: "0.7.x" 25 | activate-environment: ${{ inputs.activate-environment }} 26 | - if: inputs.sync == 'true' && inputs.dev == 'false' 27 | run: uv sync --frozen --no-dev 28 | shell: bash 29 | env: 30 | FORCE_COLOR: "1" 31 | - if: inputs.sync == 'true' && inputs.dev == 'true' 32 | run: uv sync --frozen 33 | shell: bash 34 | env: 35 | FORCE_COLOR: "1" 36 | -------------------------------------------------------------------------------- /.github/workflows/code-checkers.yml: -------------------------------------------------------------------------------- 1 | name: Static code checkers 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | mypy: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | - uses: ./.github/actions/uv-setup/ 19 | - run: mypy --install-types --non-interactive . 20 | 21 | pyright: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | persist-credentials: false 27 | - uses: ./.github/actions/uv-setup/ 28 | - run: pyright 29 | 30 | ruff: 31 | runs-on: ubuntu-latest 32 | env: 33 | FORCE_COLOR: "1" 34 | steps: 35 | - uses: actions/checkout@v4 36 | with: 37 | persist-credentials: false 38 | - uses: ./.github/actions/uv-setup/ 39 | - run: ruff check 40 | 41 | flake8: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | persist-credentials: false 47 | - uses: ./.github/actions/uv-setup/ 48 | - run: flake8 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create a release from tag 2 | 3 | permissions: {} 4 | 5 | on: 6 | push: 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | build: 12 | name: Build and store python artifacts 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | persist-credentials: false 21 | 22 | - uses: ./.github/actions/uv-setup/ 23 | 24 | - name: Build 25 | run: uv build 26 | 27 | - name: Store python distribution artifacts 28 | uses: actions/upload-artifact@v4 29 | with: 30 | name: artifacts 31 | path: dist/ 32 | 33 | release: 34 | permissions: 35 | contents: write # allow creating a release 36 | 37 | name: "Create and package a release" 38 | runs-on: ubuntu-latest 39 | needs: [build] 40 | steps: 41 | - name: Retrieve distribution artifacts 42 | uses: actions/download-artifact@v4 43 | with: 44 | name: artifacts 45 | path: dist/ 46 | 47 | - name: Create release ${{ github.ref_name }} 48 | shell: bash 49 | run: | 50 | gh release create ${GITHUB_REF_NAME} --repo ${{ github.repository }} --generate-notes dist/* 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /container/files/CentOS-Vault.repo.in: -------------------------------------------------------------------------------- 1 | [C@CENTOS_VERSION@-base] 2 | name=CentOS-@CENTOS_VERSION@ - Base 3 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/os/$basearch/ 4 | gpgcheck=1 5 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 6 | exclude=ocaml* 7 | enabled=1 8 | 9 | [C@CENTOS_VERSION@-updates] 10 | name=CentOS-@CENTOS_VERSION@ - Updates 11 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/updates/$basearch/ 12 | gpgcheck=1 13 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 14 | exclude=ocaml* 15 | enabled=1 16 | 17 | [C@CENTOS_VERSION@-extras] 18 | name=CentOS-@CENTOS_VERSION@ - Extras 19 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/extras/$basearch/ 20 | gpgcheck=1 21 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 22 | exclude=ocaml* 23 | enabled=1 24 | 25 | [C@CENTOS_VERSION@-base-source] 26 | name=CentOS-@CENTOS_VERSION@ - Base 27 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/os/Source/ 28 | gpgcheck=1 29 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 30 | exclude=ocaml* 31 | enabled=0 32 | 33 | [C@CENTOS_VERSION@-updates-source] 34 | name=CentOS-@CENTOS_VERSION@ - Updates 35 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/updates/Source/ 36 | gpgcheck=1 37 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 38 | exclude=ocaml* 39 | enabled=0 40 | 41 | [C@CENTOS_VERSION@-extras-source] 42 | name=CentOS-@CENTOS_VERSION@ - Extras 43 | baseurl=http://vault.centos.org/@CENTOS_VERSION@/extras/Source/ 44 | gpgcheck=1 45 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 46 | exclude=ocaml* 47 | enabled=0 48 | -------------------------------------------------------------------------------- /container/files/yum.conf.xs: -------------------------------------------------------------------------------- 1 | [main] 2 | cachedir=/tmp/yum 3 | keepcache=0 4 | debuglevel=2 5 | logfile=/var/log/yum.log 6 | exactarch=1 7 | obsoletes=1 8 | gpgcheck=0 9 | plugins=1 10 | installonly_limit=3 11 | reposdir=/etc/yum.repos.d.xs 12 | 13 | # This is the default, if you make this bigger yum won't see if the metadata 14 | # is newer on the remote and so you'll "gain" the bandwidth of not having to 15 | # download the new metadata and "pay" for it by yum not having correct 16 | # information. 17 | # It is esp. important, to have correct metadata, for distributions like 18 | # Fedora which don't keep old packages around. If you don't like this checking 19 | # interupting your command line usage, it's much better to have something 20 | # manually check the metadata once an hour (yum-updatesd will do this). 21 | # metadata_expire=90m 22 | 23 | # PUT YOUR REPOS HERE OR IN separate files named file.repo 24 | # in /etc/yum.repos.d 25 | 26 | [epel] 27 | name = epel 28 | enabled = 1 29 | baseurl = https://repo.citrite.net/ctx-remote-yum-fedora/epel/7/x86_64/ 30 | exclude = ocaml* 31 | gpgcheck = 0 32 | 33 | [base] 34 | name = base 35 | enabled = 1 36 | baseurl = https://repo.citrite.net/centos/7.2.1511/os/x86_64/ 37 | exclude = ocaml* 38 | gpgcheck = 0 39 | 40 | [updates] 41 | name = updates 42 | enabled = 1 43 | baseurl = https://repo.citrite.net/centos/7.2.1511/os/x86_64/ 44 | exclude = ocaml* 45 | gpgcheck = 0 46 | 47 | [extras] 48 | name = extras 49 | enabled = 1 50 | baseurl = https://repo.citrite.net/centos/7.2.1511/os/x86_64/ 51 | exclude = ocaml* 52 | gpgcheck = 0 53 | 54 | -------------------------------------------------------------------------------- /container/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | SELF_NAME=$0 5 | 6 | die() { 7 | echo >&2 8 | echo >&2 "ERROR: $*" 9 | echo >&2 10 | exit 1 11 | } 12 | 13 | die_usage() { 14 | usage >&2 15 | die "$*" 16 | } 17 | 18 | usage() { 19 | cat < 21 | ... where is a 'x.y' version such as 8.0. 22 | 23 | --platform override the default platform for the build container. 24 | EOF 25 | } 26 | 27 | PLATFORM= 28 | while [ $# -ge 1 ]; do 29 | case "$1" in 30 | --help|-h) 31 | usage 32 | exit 0 33 | ;; 34 | --platform) 35 | [ $# -ge 2 ] || die_usage "$1 needs an argument" 36 | PLATFORM="$2" 37 | shift 38 | ;; 39 | -*) 40 | die_usage "unknown flag '$1'" 41 | ;; 42 | *) 43 | break 44 | ;; 45 | esac 46 | shift 47 | done 48 | 49 | [ -n "$1" ] || die_usage "version parameter missing" 50 | 51 | RUNNER="" 52 | if [ -n "$XCPNG_OCI_RUNNER" ]; then 53 | RUNNER="$XCPNG_OCI_RUNNER" 54 | else 55 | SUPPORTED_RUNNERS="docker podman" 56 | for COMMAND in $SUPPORTED_RUNNERS; do 57 | if command -v $COMMAND >/dev/null; then 58 | RUNNER="$COMMAND" 59 | break 60 | fi 61 | done 62 | if [ -z "$RUNNER" ]; then 63 | echo >&2 "cannot find a supported runner: $SUPPORTED_RUNNERS" 64 | exit 1 65 | fi 66 | fi 67 | 68 | cd $(dirname "$0") 69 | 70 | ALMA_VERSION= 71 | CENTOS_VERSION= 72 | case "$1" in 73 | 9.*) 74 | DOCKERFILE=Dockerfile-9.x 75 | ALMA_VERSION=10.0 76 | : ${PLATFORM:=linux/amd64/v2} 77 | ;; 78 | 8.*) 79 | DOCKERFILE=Dockerfile-8.x 80 | : ${PLATFORM:=linux/amd64} 81 | ;; 82 | *) 83 | echo >&2 "Unsupported release '$1'" 84 | exit 1 85 | ;; 86 | esac 87 | 88 | "$RUNNER" build \ 89 | --platform "$PLATFORM" \ 90 | -t ghcr.io/xcp-ng/xcp-ng-build-env:${1} \ 91 | --build-arg XCP_NG_BRANCH=${1} \ 92 | --ulimit nofile=1024 \ 93 | -f $DOCKERFILE . 94 | -------------------------------------------------------------------------------- /container/files/xcp-ng.repo.7.x.in: -------------------------------------------------------------------------------- 1 | [xcp-ng-builddeps] 2 | name=XCP-ng Builddeps Repository 3 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/builddeps/x86_64/ 4 | enabled=1 5 | gpgcheck=0 6 | priority=1 7 | 8 | [xcp-ng-base] 9 | name=XCP-ng Base Repository 10 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/base/x86_64/ 11 | enabled=1 12 | gpgcheck=0 13 | priority=1 14 | exclude=xcp-ng-release 15 | 16 | [xcp-ng-updates] 17 | name=XCP-ng Updates Repository 18 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/updates/x86_64/ 19 | enabled=1 20 | gpgcheck=0 21 | priority=1 22 | exclude=xcp-ng-release 23 | 24 | [xcp-ng-updates_testing] 25 | name=XCP-ng Updates Testing Repository 26 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/updates_testing/x86_64/ 27 | enabled=0 28 | gpgcheck=0 29 | priority=1 30 | exclude=xcp-ng-release 31 | 32 | [xcp-ng-extras] 33 | name=XCP-ng Extras Repository 34 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/extras/x86_64/ 35 | enabled=0 36 | gpgcheck=0 37 | priority=1 38 | 39 | [xcp-ng-extras_testing] 40 | name=XCP-ng Extras Testing Repository 41 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/extras_testing/x86_64/ 42 | enabled=0 43 | gpgcheck=0 44 | priority=1 45 | 46 | # Source repositories 47 | 48 | [xcp-ng-builddeps-src] 49 | name=XCP-ng Builddeps Source Repository 50 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/builddeps/Source/ 51 | enabled=1 52 | gpgcheck=0 53 | priority=1 54 | 55 | [xcp-ng-base-src] 56 | name=XCP-ng Base Source Repository 57 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/base/Source/ 58 | enabled=1 59 | gpgcheck=0 60 | priority=1 61 | 62 | [xcp-ng-updates-src] 63 | name=XCP-ng Updates Source Repository 64 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/updates/Source/ 65 | enabled=1 66 | gpgcheck=0 67 | priority=1 68 | 69 | [xcp-ng-updates_testing-src] 70 | name=XCP-ng Updates Testing Repository 71 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/updates_testing/Source/ 72 | enabled=0 73 | gpgcheck=0 74 | priority=1 75 | 76 | [xcp-ng-extras-src] 77 | name=XCP-ng Extras Source Repository 78 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/extras/Source/ 79 | enabled=0 80 | gpgcheck=0 81 | priority=1 82 | 83 | [xcp-ng-extras_testing-src] 84 | name=XCP-ng Extras Testing Source Repository 85 | baseurl=https://updates.xcp-ng.org/7/@XCP_NG_BRANCH@/extras_testing/Source/ 86 | enabled=0 87 | gpgcheck=0 88 | priority=1 89 | -------------------------------------------------------------------------------- /container/Dockerfile-9.x: -------------------------------------------------------------------------------- 1 | # WARNING: when bumping the release, bump the releasever together below 2 | FROM ghcr.io/almalinux/10-base:10.0 3 | 4 | # pin Almalinux version to avoid upgrade to 10.1+ 5 | RUN mkdir -p /etc/dnf/vars && echo "10.0" > /etc/dnf/vars/releasever 6 | 7 | # Add our repositories 8 | # temporary bootstrap repository 9 | COPY files/xcp-ng-8.99.repo /etc/yum.repos.d/xcp-ng.repo 10 | # Almalinux 10 devel 11 | COPY files/Alma10-devel.repo /etc/yum.repos.d/ 12 | 13 | # Install GPG key 14 | RUN curl -sSf https://xcp-ng.org/RPM-GPG-KEY-xcpng -o /etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 15 | 16 | # Update 17 | RUN dnf update -y \ 18 | # Common build requirements 19 | && dnf install -y \ 20 | gcc \ 21 | gcc-c++ \ 22 | git \ 23 | make \ 24 | rpm-build \ 25 | redhat-rpm-config \ 26 | python3-rpm \ 27 | sudo \ 28 | dnf-plugins-core \ 29 | epel-release \ 30 | # EPEL: needs epel-release installed first 31 | && dnf install -y \ 32 | epel-rpm-macros \ 33 | almalinux-git-utils \ 34 | # Niceties 35 | && dnf install -y \ 36 | bash-completion \ 37 | vim \ 38 | wget \ 39 | which \ 40 | # -release*, to be commented out to boostrap the build-env until it gets built 41 | # FIXME: isn't it already pulled as almalinux-release when available? 42 | && dnf install -y \ 43 | xcp-ng-release \ 44 | xcp-ng-release-presets \ 45 | # clean package cache to avoid download errors 46 | && yum clean all 47 | 48 | # enable repositories commonly required to build 49 | RUN dnf config-manager --enable crb 50 | 51 | # workaround sudo not working (e.g. in podman 4.9.3 in Ubuntu 24.04) 52 | RUN chmod 0400 /etc/shadow 53 | 54 | # create the builder user 55 | RUN groupadd -g 1000 builder \ 56 | && useradd -u 1000 -g 1000 builder \ 57 | && echo "builder:builder" | chpasswd \ 58 | && echo "builder ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers 59 | 60 | RUN mkdir -p /usr/local/bin 61 | RUN curl -fsSL "https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64" -o /usr/local/bin/gosu \ 62 | && chmod +x /usr/local/bin/gosu 63 | COPY files/init-container.sh /usr/local/bin/init-container.sh 64 | COPY files/entrypoint.sh /usr/local/bin/entrypoint.sh 65 | # FIXME: check it we really need any of this 66 | # COPY --chown=builder:builder files/rpmmacros /home/builder/.rpmmacros 67 | 68 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 69 | CMD ["bash"] 70 | -------------------------------------------------------------------------------- /container/Dockerfile-8.x: -------------------------------------------------------------------------------- 1 | ARG CENTOS_VERSION=7.5.1804 2 | 3 | FROM centos:${CENTOS_VERSION} 4 | 5 | # Remove all repositories 6 | RUN rm /etc/yum.repos.d/* 7 | 8 | # Add only the specific CentOS 7.5 repositories, because that's what XS used for the majority of packages 9 | ARG CENTOS_VERSION 10 | COPY files/CentOS-Vault.repo.in /etc/yum.repos.d/CentOS-Vault-7.5.repo 11 | RUN sed -i -e "s/@CENTOS_VERSION@/${CENTOS_VERSION}/g" /etc/yum.repos.d/CentOS-Vault-7.5.repo 12 | 13 | # Add our repositories 14 | # Repository file depends on the target version of XCP-ng, and is pre-processed by build.sh 15 | ARG XCP_NG_BRANCH=8.3 16 | COPY files/xcp-ng.repo.8.x.in /etc/yum.repos.d/xcp-ng.repo 17 | RUN sed -i -e "s/@XCP_NG_BRANCH@/${XCP_NG_BRANCH}/g" /etc/yum.repos.d/xcp-ng.repo 18 | 19 | # Install GPG key 20 | RUN curl -sSf https://xcp-ng.org/RPM-GPG-KEY-xcpng -o /etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 21 | 22 | # Update 23 | RUN yum update -y \ 24 | # Fix invalid rpmdb checksum error with overlayfs, see https://github.com/docker/docker/issues/10180 25 | # (still needed?) 26 | && yum install -y yum-plugin-ovl \ 27 | # Use priorities so that packages from our repositories are preferred over those from CentOS repositories 28 | && yum install -y yum-plugin-priorities \ 29 | # Common build requirements 30 | && yum install -y \ 31 | gcc \ 32 | gcc-c++ \ 33 | git \ 34 | make \ 35 | rpm-build \ 36 | redhat-rpm-config \ 37 | rpm-python \ 38 | sudo \ 39 | yum-utils \ 40 | epel-release \ 41 | epel-rpm-macros \ 42 | # Niceties 43 | && yum install -y \ 44 | vim \ 45 | wget \ 46 | which \ 47 | # clean package cache to avoid download errors 48 | && yum clean all 49 | 50 | # OCaml in XS may be older than in CentOS 51 | RUN sed -i "/gpgkey/a exclude=ocaml*" /etc/yum.repos.d/Cent* /etc/yum.repos.d/epel* 52 | 53 | # create the builder user 54 | RUN groupadd -g 1000 builder \ 55 | && useradd -u 1000 -g 1000 builder \ 56 | && echo "builder:builder" | chpasswd \ 57 | && echo "builder ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers 58 | 59 | RUN mkdir -p /usr/local/bin 60 | RUN curl -fsSL "https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64" -o /usr/local/bin/gosu \ 61 | && chmod +x /usr/local/bin/gosu 62 | COPY files/init-container.sh /usr/local/bin/init-container.sh 63 | COPY files/entrypoint.sh /usr/local/bin/entrypoint.sh 64 | COPY --chown=builder:builder files/rpmmacros /home/builder/.rpmmacros 65 | 66 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 67 | CMD ["bash"] 68 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "xcp-ng-dev" 3 | description = "A tool to develop and build XCP-ng packages" 4 | readme = "README.md" 5 | requires-python = ">=3.11, <4" 6 | license = "MIT" 7 | license-files = ["LICENSE"] 8 | dynamic = ["version"] 9 | dependencies = [ 10 | "argcomplete", 11 | ] 12 | 13 | [dependency-groups] 14 | dev = [ 15 | "icecream", 16 | "mypy", 17 | "flake8", 18 | "pyright", 19 | "ruff", 20 | "typing-extensions", 21 | "flake8-pyproject", 22 | ] 23 | 24 | [project.scripts] 25 | xcp-ng-dev = "xcp_ng_dev.cli:main" 26 | 27 | [project.urls] 28 | Homepage = "https://github.com/xcp-ng/xcp-ng-build-env/" 29 | 30 | [build-system] 31 | requires = ["setuptools >= 77.0.3", "setuptools-scm>=8"] 32 | build-backend = "setuptools.build_meta" 33 | 34 | [tool.setuptools_scm] 35 | 36 | [tool.pyright] 37 | typeCheckingMode = "standard" 38 | 39 | [tool.ruff] 40 | preview = true 41 | line-length = 120 42 | exclude = [".git"] 43 | 44 | [tool.ruff.format] 45 | quote-style = "preserve" 46 | 47 | [tool.ruff.lint] 48 | select = [ 49 | "D", # pydocstyle 50 | "F", # Pyflakes 51 | "I", # isort 52 | "SLF", # flake8-self 53 | "SIM", # flake8-simplify 54 | ] 55 | # don't use some of the default D and SIM rules 56 | ignore = [ 57 | "D100", # undocumented-public-module 58 | "D101", # undocumented-public-class 59 | "D102", # undocumented-public-method 60 | "D103", # undocumented-public-function 61 | "D104", # undocumented-public-package 62 | "D105", # undocumented-magic-method 63 | "D106", # undocumented-public-nested-class 64 | "D107", # undocumented-public-init 65 | "D200", # unnecessary-multiline-docstring 66 | "D203", # incorrect-blank-line-before-class 67 | "D204", # incorrect-blank-line-after-class 68 | "D205", # missing-blank-line-after-summary 69 | "D210", # surrounding-whitespace 70 | "D212", # incorrect-blank-line-before-class 71 | "D400", # missing-trailing-period 72 | "D401", # non-imperative-mood 73 | "D403", # first-word-uncapitalized 74 | "SIM105", # suppressible-exception 75 | "SIM108", # if-else-block-instead-of-if-exp 76 | ] 77 | 78 | # restrict to the PEP 257 rules 79 | pydocstyle.convention = "pep257" 80 | 81 | [tool.ruff.lint.isort.sections] 82 | testing = ["pytest*"] 83 | typing = ["typing"] 84 | 85 | [tool.ruff.lint.isort] 86 | lines-after-imports = 1 87 | section-order = [ 88 | "future", 89 | "testing", 90 | "standard-library", 91 | "third-party", 92 | "first-party", 93 | "local-folder", 94 | "typing", 95 | ] 96 | 97 | # ruff doesn't provide all the pycodestyle rules, and pycodestyle is not well 98 | # supported by some IDEs, so we use flake8 for that 99 | [tool.flake8] 100 | max-line-length = 120 101 | ignore = [ 102 | "E261", # At least two spaces before inline comment 103 | "E302", # Expected 2 blank lines, found 0 104 | "E305", # Expected 2 blank lines after end of function or class 105 | "W503", # Line break occurred before a binary operator 106 | "F", # already done by ruff 107 | ] 108 | exclude=[".git", ".venv"] 109 | -------------------------------------------------------------------------------- /container/files/init-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -n "$SCRIPT_DEBUG" ]; then 5 | set -x 6 | fi 7 | 8 | if [ -n "$NO_EXIT" ]; then 9 | trap "/bin/bash --login" EXIT 10 | fi 11 | 12 | os_release() 13 | { 14 | ( 15 | source /etc/os-release 16 | echo "$VERSION_ID" 17 | ) 18 | } 19 | 20 | OS_RELEASE=$(os_release) 21 | 22 | # get list of user repos 23 | case "$OS_RELEASE" in 24 | 8.2.*) XCPREL=8/8.2 ;; 25 | 8.3.*) XCPREL=8/8.3 ;; 26 | *) echo >&2 "WARNING: unknown release, not fetching user repo definitions" ;; 27 | esac 28 | 29 | if [ -n "$XCPREL" ]; then 30 | curl -s https://koji.xcp-ng.org/repos/user/${XCPREL}/xcpng-users.repo | 31 | sed '/^gpgkey=/ ipriority=1' | sudo tee /etc/yum.repos.d/xcp-ng-users.repo > /dev/null 32 | fi 33 | 34 | # yum or dnf? 35 | case "$OS_RELEASE" in 36 | 8.2.*|8.3.*) 37 | DNF=yum 38 | CFGMGR=yum-config-manager 39 | BDEP=yum-builddep 40 | ;; 41 | 8.99.*|9.*|10.*) # FIXME 10.* actually to bootstrap Alma10 42 | DNF=dnf 43 | CFGMGR="dnf config-manager" 44 | BDEP="dnf builddep" 45 | ;; 46 | *) echo >&2 "ERROR: unknown release, cannot know package manager"; exit 1 ;; 47 | esac 48 | 49 | # disable repositories if needed 50 | if [ -n "$DISABLEREPO" ]; then 51 | sudo $CFGMGR --disable "$DISABLEREPO" 52 | fi 53 | 54 | # enable additional repositories if needed 55 | if [ -n "$ENABLEREPO" ]; then 56 | sudo $CFGMGR --enable "$ENABLEREPO" 57 | fi 58 | 59 | if [ -z "$NOUPDATE" ]; then 60 | # update to either install newer updates or to take packages from added repos into account 61 | sudo $DNF update -y --disablerepo=epel 62 | fi 63 | 64 | cd "$HOME" 65 | 66 | # double the default stack size 67 | ulimit -s 16384 68 | 69 | # get the package arch used in the container (eg. "x86_64_v2") 70 | RPMARCH=$(rpm -q glibc --qf "%{arch}") 71 | 72 | if [ -n "$BUILD_LOCAL" ]; then 73 | time ( 74 | cd ~/rpmbuild 75 | rm BUILD BUILDROOT RPMS SRPMS -rf 76 | 77 | if specs=$(ls *.spec 2>/dev/null); then 78 | SPECFLAGS=( 79 | --define "_sourcedir $PWD" 80 | --define "_specdir $PWD" 81 | ) 82 | else 83 | specs=$(ls SPECS/*.spec 2>/dev/null) 84 | # SOURCES/ and SPECS/ are still the default in Alma10 85 | SPECFLAGS=() 86 | fi 87 | echo "Found specfiles $specs" 88 | 89 | case "$OS_RELEASE" in 90 | 8.2.*|8.3.*) ;; # sources always available via git-lfs 91 | 8.99.*|9.*) if [ -r sources ]; then alma_get_sources -i sources; fi ;; 92 | *) echo >&2 "ERROR: unknown release, cannot know package manager"; exit 1 ;; 93 | esac 94 | 95 | sudo $BDEP "${SPECFLAGS[@]}" -y $specs 96 | 97 | : ${RPMBUILD_STAGE:=a} # default if not specified: -ba 98 | RPMBUILDFLAGS=( 99 | -b${RPMBUILD_STAGE} $specs 100 | --target "$RPMARCH" 101 | $RPMBUILD_OPTS 102 | "${SPECFLAGS[@]}" 103 | ) 104 | # in case the build deps contain xs-opam-repo, source the added profile.d file 105 | [ ! -f /etc/profile.d/opam.sh ] || source /etc/profile.d/opam.sh 106 | if [ $? == 0 ]; then 107 | if [ -n "$RPMBUILD_DEFINE" ]; then 108 | RPMBUILDFLAGS+=(--define "$RPMBUILD_DEFINE") 109 | fi 110 | rpmbuild "${RPMBUILDFLAGS[@]}" 111 | if [ $? == 0 -a -d ~/output/ ]; then 112 | cp -rf RPMS SRPMS ~/output/ 113 | fi 114 | fi 115 | ) 116 | elif [ -n "$COMMAND" ]; then 117 | $COMMAND 118 | else 119 | /bin/bash --login || true 120 | fi 121 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image to GHCR 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | pull_request: 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | xcp-ng-build-env-82: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | packages: write # Required to push packages to GHCR 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | persist-credentials: false 20 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 21 | with: 22 | driver: docker-container 23 | - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 24 | if: github.ref == 'refs/heads/master' 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} # Uses the GitHub user/org name that triggered the workflow 28 | password: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub 29 | - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 30 | with: 31 | context: ./container 32 | file: ./container/Dockerfile-8.x 33 | push: ${{ github.ref == 'refs/heads/master' }} 34 | tags: ghcr.io/${{ github.repository }}:8.2 35 | cache-from: type=gha,scope=${{ github.ref_name }}-82 # Cache layers to speed up builds 36 | cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-82 # Store layers in cache for future builds 37 | build-args: | 38 | XCP_NG_BRANCH=8.2 39 | platforms: | 40 | linux/amd64 41 | 42 | xcp-ng-build-env-83: 43 | runs-on: ubuntu-latest 44 | permissions: 45 | packages: write # Required to push packages to GHCR 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | persist-credentials: false 50 | - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 51 | with: 52 | driver: docker-container 53 | - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 54 | if: github.ref == 'refs/heads/master' 55 | with: 56 | registry: ghcr.io 57 | username: ${{ github.actor }} # Uses the GitHub user/org name that triggered the workflow 58 | password: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub 59 | - run: echo "VERSION=$(cat ./src/xcp_ng_dev/files/protocol-version.txt | tr -d '\n')" >> $GITHUB_ENV 60 | - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 61 | with: 62 | context: ./container 63 | file: ./container/Dockerfile-8.x 64 | push: ${{ github.ref == 'refs/heads/master' }} 65 | tags: ghcr.io/${{ github.repository }}:8.3 66 | cache-from: type=gha,scope=${{ github.ref_name }}-83 # Cache layers to speed up builds 67 | cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-83 # Store layers in cache for future builds 68 | platforms: | 69 | linux/amd64 70 | 71 | # TODO: uncomment once we have a public xcp-ng 9.0 repository 72 | # xcp-ng-build-env-90: 73 | # runs-on: ubuntu-latest 74 | # permissions: 75 | # packages: write # Required to push packages to GHCR 76 | # steps: 77 | # - uses: actions/checkout@v4 78 | # with: 79 | # persist-credentials: false 80 | # - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 81 | # with: 82 | # driver: docker-container 83 | # - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 84 | # if: github.ref == 'refs/heads/master' 85 | # with: 86 | # registry: ghcr.io 87 | # username: ${{ github.actor }} # Uses the GitHub user/org name that triggered the workflow 88 | # password: ${{ secrets.GITHUB_TOKEN }} # Automatically provided by GitHub 89 | # - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 90 | # with: 91 | # context: ./container 92 | # file: ./container/Dockerfile-9.x 93 | # platforms: | 94 | # linux/amd64/v2 95 | # push: ${{ github.ref == 'refs/heads/master' }} 96 | # tags: ghcr.io/${{ github.repository }}:9.0 97 | # cache-from: type=gha,scope=${{ github.ref_name }}-90 # Cache layers to speed up builds 98 | # cache-to: type=gha,mode=max,scope=${{ github.ref_name }}-90 # Store layers in cache for future builds 99 | -------------------------------------------------------------------------------- /container/files/xcp-ng.repo.8.x.in: -------------------------------------------------------------------------------- 1 | [xcp-ng-base] 2 | name=XCP-ng Base Repository 3 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/base/x86_64/ 4 | enabled=1 5 | gpgcheck=1 6 | repo_gpgcheck=1 7 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 8 | priority=1 9 | 10 | [xcp-ng-updates] 11 | name=XCP-ng Updates Repository 12 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/updates/x86_64/ 13 | enabled=1 14 | gpgcheck=1 15 | repo_gpgcheck=1 16 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 17 | priority=1 18 | 19 | [xcp-ng-candidates] 20 | name=XCP-ng Candidates Repository 21 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/candidates/x86_64/ 22 | enabled=1 23 | gpgcheck=1 24 | repo_gpgcheck=1 25 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 26 | priority=1 27 | 28 | [xcp-ng-testing] 29 | name=XCP-ng Testing Repository 30 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/testing/x86_64/ 31 | enabled=1 32 | gpgcheck=1 33 | repo_gpgcheck=1 34 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 35 | priority=1 36 | 37 | [xcp-ng-ci] 38 | name=XCP-ng CI Repository 39 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/ci/x86_64/ 40 | enabled=1 41 | gpgcheck=1 42 | repo_gpgcheck=1 43 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 44 | priority=1 45 | 46 | [xcp-ng-incoming] 47 | name=XCP-ng Incoming Repository 48 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/incoming/x86_64/ 49 | enabled=0 50 | gpgcheck=1 51 | repo_gpgcheck=1 52 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 53 | priority=1 54 | 55 | 56 | [xcp-ng-lab] 57 | name=XCP-ng Lab Source Repository 58 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/lab/x86_64/ 59 | enabled=0 60 | gpgcheck=1 61 | repo_gpgcheck=1 62 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 63 | priority=1 64 | 65 | [xcp-ng-linstor] 66 | name=XCP-ng LINSTOR Repository 67 | baseurl=https://repo.vates.tech/xcp-ng/8/@XCP_NG_BRANCH@/linstor/x86_64/ 68 | enabled=0 69 | gpgcheck=1 70 | repo_gpgcheck=1 71 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 72 | priority=1 73 | 74 | [xcp-ng-linstor-testing] 75 | name=XCP-ng LINSTOR Testing Repository 76 | baseurl=https://repo.vates.tech/xcp-ng/8/@XCP_NG_BRANCH@/linstor-testing/x86_64/ 77 | enabled=0 78 | gpgcheck=1 79 | repo_gpgcheck=1 80 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 81 | priority=1 82 | 83 | # Source repositories 84 | 85 | [xcp-ng-base-src] 86 | name=XCP-ng Base Source Repository 87 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/base/Source/ 88 | enabled=1 89 | gpgcheck=1 90 | repo_gpgcheck=1 91 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 92 | priority=1 93 | 94 | [xcp-ng-updates-src] 95 | name=XCP-ng Updates Source Repository 96 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/updates/Source/ 97 | enabled=1 98 | gpgcheck=1 99 | repo_gpgcheck=1 100 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 101 | priority=1 102 | 103 | [xcp-ng-candidates-src] 104 | name=XCP-ng Candidates Source Repository 105 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/candidates/Source/ 106 | enabled=1 107 | gpgcheck=1 108 | repo_gpgcheck=1 109 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 110 | priority=1 111 | 112 | [xcp-ng-testing-src] 113 | name=XCP-ng Testing Source Repository 114 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/testing/Source/ 115 | enabled=1 116 | gpgcheck=1 117 | repo_gpgcheck=1 118 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 119 | priority=1 120 | 121 | [xcp-ng-ci-src] 122 | name=XCP-ng CI Source Repository 123 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/ci/Source/ 124 | enabled=1 125 | gpgcheck=1 126 | repo_gpgcheck=1 127 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 128 | priority=1 129 | 130 | [xcp-ng-incoming-src] 131 | name=XCP-ng Incoming Source Repository 132 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/incoming/Source/ 133 | enabled=0 134 | gpgcheck=1 135 | repo_gpgcheck=1 136 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 137 | priority=1 138 | 139 | [xcp-ng-lab-src] 140 | name=XCP-ng Lab Source Repository 141 | baseurl=http://mirrors.xcp-ng.org/8/@XCP_NG_BRANCH@/lab/Source/ 142 | enabled=0 143 | gpgcheck=1 144 | repo_gpgcheck=1 145 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 146 | priority=1 147 | 148 | [xcp-ng-linstor-src] 149 | name=XCP-ng LINSTOR Repository 150 | baseurl=https://repo.vates.tech/xcp-ng/8/@XCP_NG_BRANCH@/linstor/Source/ 151 | enabled=0 152 | gpgcheck=1 153 | repo_gpgcheck=1 154 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 155 | priority=1 156 | 157 | [xcp-ng-linstor-testing-src] 158 | name=XCP-ng LINSTOR Testing Repository 159 | baseurl=https://repo.vates.tech/xcp-ng/8/@XCP_NG_BRANCH@/linstor-testing/Source/ 160 | enabled=0 161 | gpgcheck=1 162 | repo_gpgcheck=1 163 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-xcpng 164 | priority=1 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xcp-ng-build-env 2 | 3 | This container config and collection of supporting scripts allows for 4 | creating a container to work on and build a XCP-ng package from an 5 | SRPM or from a directory containing a `SOURCES/` and a `SPECS/` 6 | directory along with appropriate RPM spec file and software sources. 7 | 8 | It will build a container with the right build environment (including some 9 | useful tools). 10 | Depending on the parameters, it will either do everything automatically to build a 11 | given package, or just install build-dependencies and let you work manually from a shell 12 | in the container context. Or even just start the container and let you do anything you 13 | want. 14 | 15 | ## Configuration 16 | 17 | You'll need to install docker or podman. Podman should be available 18 | from your distro repositories, for Docker follow the instructions for 19 | your platform on https://www.docker.com/ 20 | 21 | If you have both installed, docker will be used by default. If you 22 | want to use a specific container runtime, set `XCPNG_OCI_RUNNER` to 23 | the docker-compatible command to use (typically `podman` or `docker`). 24 | 25 | You'll need to install git-lfs to be able to download the source tarballs from 26 | git, otherwise when running xcp-ng-dev, it won't be able to extract the sources. 27 | 28 | ## Installation 29 | 30 | This can be done with `uv`: 31 | ``` 32 | uv tool install --from git+https://github.com/xcp-ng/xcp-ng-build-env xcp-ng-dev 33 | ``` 34 | or `pipx:` 35 | ``` 36 | pipx install git+https://github.com/xcp-ng/xcp-ng-build-env 37 | ``` 38 | 39 | After this, a new command will be available: `xcp-ng-dev`. 40 | 41 | If you want to develop the package and try the changes as you develop 42 | the package, or prefer updating the version from a git repo rather 43 | than from the python tools, clone the repository and install the 44 | `xcp-ng-dev` package: 45 | 46 | ```bash 47 | git clone github.com:xcp-ng/xcp-ng-build-env 48 | cd xcp-ng-build-env 49 | uv tool install --editable . 50 | ``` 51 | 52 | If `uv` is not available you can use other tools to install python packages, 53 | like `pipx install --editable .` 54 | 55 | If you do not want this behaviour, use: `uv tool install --from . xcp-ng-dev` 56 | or `pipx install .` 57 | 58 | ### updating 59 | 60 | Depending on how you installed: 61 | * with `uv`: just run the same command 62 | * with `pipx`: run the same command, with `--force` 63 | * editable with `git`: just update your git working tree 64 | 65 | Then download the latest image with `docker pull ghcr.io/xcp-ng/xcp-ng-build-env:8.3` or `podman pull ghcr.io/xcp-ng/xcp-ng-build-env:8.3`. 66 | 67 | ## Completion 68 | 69 | ### Bash 70 | 71 | To install the completion, add `eval "$(register-python-argcomplete xcp-ng-dev)"` to `~/.bash_completion` and relaunch Bash. 72 | 73 | ### Zsh 74 | 75 | To install the completion, add `eval "$(register-python-argcomplete xcp-ng-dev)"` to `~/.zshrc` and relaunch Zsh. 76 | 77 | ### Fish 78 | 79 | To install the completion, run `register-python-argcomplete --shell fish xcp-ng-dev > ~/.config/fish/completions/xcp-ng-dev.fish` and relaunch fish. 80 | 81 | ## Building the container image(s) 82 | 83 | > [!NOTE] 84 | > The images are typically downloaded from ghcr.io for regular usage. 85 | > Unless you're working on this project, you usually don't need to build the container images yourself. 86 | 87 | You need one container image per target version of XCP-ng. 88 | 89 | Clone this repository (outside any container), then use `./container/build.sh` to 90 | generate the image. Adapt the version to the wanted release of XCP-ng. 91 | Note that Docker and Podman store container images separately. 92 | 93 | ``` 94 | Usage: ./container/build.sh [--platform PF] 95 | ... where is a 'x.y' version such as 8.0. 96 | ``` 97 | 98 | ## Using the container 99 | 100 | Use `xcp-ng-dev`. It accepts a variety of parameters allowing for different uses: 101 | * rebuild an existing source RPM (with automated installation of the build dependencies) 102 | * build a package from an already extracted source RPM (sources and spec file), or from a directory that follows the rpmbuild convention (a `SOURCES/` directory and a `SPECS/` directory). Most useful for building packages from XCP-ng's git repositories of RPM sources: https://github.com/xcp-ng-rpms. 103 | * or simply start a shell in the build environment, with the appropriate CentOS, EPEL and XCP-ng yum repositories enabled. 104 | 105 | **Examples** 106 | 107 | Build from git (and put the result into RPMS/ and SRPMS/ subdirectories) 108 | ```sh 109 | # Find the relevant repository at https://github.com/xcp-ng-rpms/ 110 | # Make sure you have git-lfs installed before cloning. 111 | # Then... (Example taken: xapi) 112 | git clone https://github.com/xcp-ng-rpms/xapi.git 113 | 114 | # ... Here add your patches ... 115 | 116 | # Build. 117 | xcp-ng-dev container build 8.2 xapi/ 118 | ``` 119 | 120 | **Important switches** 121 | 122 | * `--no-exit` drops you to a shell after the build, instead of closing the container. Useful if the build fails and you need to debug. 123 | * `--no-rm` keeps the container on exit, if you want to keep it for whatever reason. If you do, you can still reclaim space afterwards by running `docker container prune` and `docker image prune`. 124 | * `-v` / `--volume` (see *Mounting repos from outside the container* below) 125 | 126 | **Refreshing fuzzy patches** 127 | 128 | In XCP-ng 9.0, `rpmbuild` rejects fuzzy patches. The easiest-known 129 | way to get them refreshed is to let `quilt` do the job, but that's not 130 | fully automated. 131 | 132 | 1. modify the specfile to add `-Squilt` to `%autosetup` or 133 | `%autopatch` in the `%prep` block; add `BuildRequires: quilt` 134 | 2. let quilt apply them in a 8.3 buildenv (`quilt` in 8.3 is only in EPEL) and get you a shell: 135 | ```sh 136 | xcp-ng-dev container build --rpmbuild-stage=p -n --enablerepo=epel 8.3 137 | ``` 138 | 3. ask `quilt` to refresh all your patches (alternatively just the one you want) 139 | ```sh 140 | cd rpmbuild/BUILD/$dir 141 | quilt pop -a --refresh 142 | cp patches/* ../../SOURCES/ 143 | ``` 144 | 4. carefully pick up the bits you need 145 | 146 | Note: unfortunately `rpmbuild` (in 8.3 at least) does not add all 147 | patches in `patches/series` upfront, so in case of real conflict this 148 | has to be redone from step 2 each time. 149 | 150 | ## Building packages manually 151 | 152 | If you need to build packages manually, here are some useful commands 153 | 154 | Install the dependencies of the package using yum: 155 | 156 | ```sh 157 | yum-builddep xapi 158 | ``` 159 | 160 | then either download the SRPM using yumdownloader and rebuild it: 161 | 162 | ```sh 163 | yumdownloader --source xapi 164 | rpmbuild --rebuild xapi* 165 | ``` 166 | 167 | or build from upstream sources, without producing RPMs: 168 | 169 | ```sh 170 | git clone git://github.com/xapi-project/xen-api 171 | cd xen-api 172 | ./configure 173 | make 174 | ``` 175 | 176 | ## Mounting external directories into the container 177 | 178 | If you'd like to develop using the tools on your host and preserve the changes 179 | to source and revision control but still use the container for building, you 180 | can do so by mounting a volume in the container, using the `-v` option to mount 181 | a directory from your host to a suitable point inside the container. For 182 | example, if I clone some repos into a directory on my host, say `/work/code/`, 183 | then I can mount it inside the container as follows: 184 | 185 | ```sh 186 | xcp-ng-dev container shell -v /work/code:/mnt/repos 8.2 187 | ``` 188 | -------------------------------------------------------------------------------- /src/xcp_ng_dev/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # PYTHON_ARGCOMPLETE_OK 4 | 5 | """ 6 | Thin wrapper around "docker run" or "podman run". 7 | 8 | Simplifies the creation of a build environment for XCP-ng packages. 9 | """ 10 | 11 | import argparse 12 | import os 13 | import shutil 14 | import subprocess 15 | import sys 16 | 17 | import argcomplete 18 | 19 | CONTAINER_PREFIX = "ghcr.io/xcp-ng/xcp-ng-build-env" 20 | 21 | DEFAULT_ULIMIT_NOFILE = 2048 22 | RPMBUILD_STAGES = "abpfcilsrd" # valid X values in `rpmbuild -bX` 23 | 24 | RUNNER = os.getenv("XCPNG_OCI_RUNNER") 25 | if RUNNER is None: 26 | SUPPORTED_RUNNERS = "docker podman" 27 | for command in SUPPORTED_RUNNERS.split(): 28 | if shutil.which(command): 29 | RUNNER = command 30 | break 31 | else: 32 | raise Exception(f"cannot find a supported runner: {SUPPORTED_RUNNERS}") 33 | 34 | def is_podman(runner): 35 | if os.path.basename(runner) == "podman": 36 | return True 37 | return subprocess.getoutput(f"{runner} --version").startswith("podman ") 38 | 39 | def add_common_args(parser): 40 | group = parser.add_argument_group("common arguments") 41 | group.add_argument('-n', '--no-exit', action='store_true', 42 | help='After finishing the execution of the action, drop user into a shell') 43 | group.add_argument('-d', '--dir', action='append', 44 | help='Local dir to mount in the ' 45 | 'image. Will be mounted at /external/') 46 | group.add_argument('-e', '--env', action='append', 47 | help='Environment variables passed directly to ' 48 | f'{RUNNER} -e') 49 | group.add_argument('-a', '--enablerepo', 50 | help='additional repositories to enable before installing build dependencies. ' 51 | 'Same syntax as yum\'s --enablerepo parameter. Available additional repositories: ' 52 | 'check files/xcp-ng.repo.*.x.in.') 53 | group.add_argument('--disablerepo', 54 | help='disable repositories. Same syntax as yum\'s --disablerepo parameter. ' 55 | 'If both --enablerepo and --disablerepo are set, --disablerepo will be applied first') 56 | group.add_argument('--no-update', action='store_true', 57 | help='do not run "yum update" on container start, use it as it was at build time') 58 | group.add_argument('--no-network', action='store_true', 59 | help='disable all networking support in the build environment') 60 | 61 | def add_container_args(parser): 62 | group = parser.add_argument_group("container arguments") 63 | group.add_argument('container_version', 64 | help='The version of XCP-ng container to for the build. For example, 8.3.') 65 | group.add_argument('-v', '--volume', action='append', 66 | help=f'Volume mounts passed directly to {RUNNER} -v') 67 | group.add_argument('--no-rm', action='store_true', 68 | help='Do not destroy the container on exit') 69 | group.add_argument('--syslog', action='store_true', 70 | help='Enable syslog to host by mounting in /dev/log') 71 | group.add_argument('--name', help='Assign a name to the container') 72 | group.add_argument('--ulimit', action='append', 73 | help=f'Ulimit options passed directly to {RUNNER} run') 74 | group.add_argument('--platform', action='store', 75 | help="Override the default platform for the build container. " 76 | "Can notably be used to workaround podman bug #6185 fixed in v5.5.1.") 77 | group.add_argument('--debug', action='store_true', 78 | help='Enable script tracing in container initialization (sh -x)') 79 | 80 | 81 | def buildparser(): 82 | parser = argparse.ArgumentParser() 83 | subparsers_env = parser.add_subparsers( 84 | required=True, title="Development environments", 85 | help="Available environments") 86 | 87 | # container-based workflow 88 | parser_container = subparsers_env.add_parser('container', help="Use a local container to build a package") 89 | parser_container.set_defaults(func=container) 90 | subparsers_container = parser_container.add_subparsers( 91 | dest='action', required=True, 92 | help="Actions available for developing packages") 93 | 94 | # build -- build an rpm using a container 95 | parser_build = subparsers_container.add_parser( 96 | 'build', 97 | help="Install dependencies for the spec file(s) found in the SPECS/ subdirectory " 98 | "of the directory passed as parameter, then build the RPM(s). " 99 | "Built RPMs and SRPMs will be in RPMS/ and SRPMS/ subdirectories. " 100 | "Any preexisting BUILD, BUILDROOT, RPMS or SRPMS directories will be removed first.") 101 | add_common_args(parser_build) 102 | add_container_args(parser_build) 103 | group_build = parser_build.add_argument_group("build arguments") 104 | group_build.add_argument( 105 | 'source_dir', nargs='?', default='.', 106 | help="Root path where SPECS/ and SOURCES are available. " 107 | "The default is the working directory") 108 | group_build.add_argument( 109 | '--define', 110 | help="Definitions to be passed to rpmbuild. Example: --define " 111 | "'xcp_ng_section extras', for building the 'extras' " 112 | "version of a package which exists in both 'base' and 'extras' versions.") 113 | group_build.add_argument( 114 | '-o', '--output-dir', 115 | help="Directory where the RPMs, SRPMs and the build logs will appear. " 116 | "The directory is created if it doesn't exist") 117 | group_build.add_argument( 118 | '--rpmbuild-opts', action='append', 119 | help="Pass additional option(s) to rpmbuild") 120 | group_build.add_argument( 121 | '--rpmbuild-stage', action='store', 122 | help=f"Request given -bX stage rpmbuild, X in [{RPMBUILD_STAGES}]") 123 | 124 | # run -- execute commands inside a container 125 | parser_run = subparsers_container.add_parser( 126 | 'run', 127 | help='Execute a command inside a container') 128 | add_common_args(parser_run) 129 | add_container_args(parser_run) 130 | group_run = parser_run.add_argument_group("run arguments") 131 | group_run.add_argument( 132 | 'command', nargs='*', 133 | help='Command with arguments to run inside the container, ' 134 | 'if the command has arguments that start with --, ' 135 | 'separate the arguments for this tool and the command with " -- ".') 136 | 137 | # shell -- like run bash 138 | parser_shell = subparsers_container.add_parser( 139 | 'shell', 140 | help='Drop a shell into the prepared container') 141 | add_common_args(parser_shell) 142 | add_container_args(parser_shell) 143 | parser_run.add_argument_group("shell arguments") 144 | 145 | return parser 146 | 147 | def container(args): 148 | docker_args = [RUNNER, "run"] 149 | 150 | if is_podman(RUNNER): 151 | # With podman we use the `--userns` option to map the builder user to the user on the system. 152 | # The container will start with that user and not as root as with docker 153 | docker_args += ["--userns=keep-id:uid=1000,gid=1000", "--security-opt", "label=disable"] 154 | else: 155 | # With docker, the container starts as root and modify the builder user in the entrypoint to 156 | # match the uid:gid of the user launching the container, and then continue with the builder 157 | # user thanks to gosu. 158 | docker_args += ["-e", f'BUILDER_UID={os.getuid()}', "-e", f'BUILDER_GID={os.getgid()}'] 159 | 160 | # common args 161 | if args.no_exit: 162 | docker_args += ["-e", "NO_EXIT=1"] 163 | if args.dir: 164 | for localdir in args.dir: 165 | if not os.path.isdir(localdir): 166 | print("Local directory argument is not a directory!", file=sys.stderr) 167 | sys.exit(1) 168 | ext_path = os.path.abspath(localdir) 169 | int_path = os.path.basename(ext_path) 170 | docker_args += ["-v", "%s:/external/%s" % (ext_path, int_path)] 171 | if args.env: 172 | for env in args.env: 173 | docker_args += ["-e", env] 174 | if args.enablerepo: 175 | docker_args += ["-e", "ENABLEREPO=%s" % args.enablerepo] 176 | if args.disablerepo: 177 | docker_args += ["-e", "DISABLEREPO=%s" % args.disablerepo] 178 | if args.no_update: 179 | docker_args += ["-e", "NOUPDATE=1"] 180 | if args.no_network: 181 | docker_args += ["--network", "none"] 182 | 183 | if args.no_network and not args.no_update: 184 | print("WARNING: network disabled but --no-update not passed", file=sys.stderr) 185 | 186 | # container args 187 | if args.volume: 188 | for volume in args.volume: 189 | docker_args += ["-v", volume] 190 | if not args.no_rm: 191 | docker_args += ["--rm=true"] 192 | if args.syslog: 193 | docker_args += ["-v", "/dev/log:/dev/log"] 194 | if args.name: 195 | docker_args += ["--name", args.name] 196 | 197 | ulimit_nofile = False 198 | if args.ulimit: 199 | for ulimit in args.ulimit: 200 | if ulimit.startswith('nofile='): 201 | ulimit_nofile = True 202 | docker_args += ["--ulimit", ulimit] 203 | if not ulimit_nofile: 204 | docker_args += ["--ulimit", "nofile=%s" % DEFAULT_ULIMIT_NOFILE] 205 | 206 | docker_arch = args.platform or ("linux/amd64/v2" 207 | if args.container_version == "9.0" 208 | else "linux/amd64") 209 | docker_args += ["--platform", docker_arch] 210 | 211 | if args.debug: 212 | docker_args += ["-e", "SCRIPT_DEBUG=1"] 213 | 214 | # --no-exit requires a tty 215 | wants_interactive = args.no_exit 216 | 217 | # action-specific 218 | match args.action: 219 | case 'build': 220 | if args.no_network and not args.local_repo: 221 | print("WARNING: network disabled but --local-repo not passed", file=sys.stderr) 222 | 223 | build_dir = os.path.abspath(args.source_dir) 224 | if args.define: 225 | docker_args += ["-e", "RPMBUILD_DEFINE=%s" % args.define] 226 | if args.output_dir: 227 | os.makedirs(args.output_dir, exist_ok=True) 228 | docker_args += ["-v", "%s:/home/builder/output" % 229 | os.path.abspath(args.output_dir)] 230 | if args.rpmbuild_opts: 231 | docker_args += ["-e", "RPMBUILD_OPTS=%s" % ' '.join(args.rpmbuild_opts)] 232 | if args.rpmbuild_stage: 233 | if args.rpmbuild_stage not in RPMBUILD_STAGES: 234 | print(f"--rpmbuild-stage={args.rpmbuild_stage} not in '{RPMBUILD_STAGES}'", file=sys.stderr) 235 | sys.exit(1) 236 | docker_args += ["-e", f"RPMBUILD_STAGE={args.rpmbuild_stage}"] 237 | 238 | docker_args += ["-v", f"{build_dir}:/home/builder/rpmbuild"] 239 | docker_args += ["-e", "BUILD_LOCAL=1"] 240 | print(f"Building directory {build_dir}", file=sys.stderr) 241 | 242 | case 'run': 243 | docker_args += ["-e", "COMMAND=%s" % ' '.join(args.command)] 244 | 245 | case 'shell': 246 | wants_interactive = True 247 | 248 | if wants_interactive: 249 | docker_args += ["--interactive", "--tty"] 250 | 251 | # exec "docker run" 252 | docker_args += [f"{CONTAINER_PREFIX}:{args.container_version}", 253 | "/usr/local/bin/init-container.sh"] 254 | print("Launching docker with args %s" % docker_args, file=sys.stderr) 255 | return subprocess.call(docker_args) 256 | 257 | def main(): 258 | """ Main entry point. """ 259 | parser = buildparser() 260 | 261 | argcomplete.autocomplete(parser) 262 | 263 | args = parser.parse_args() 264 | 265 | return_code = args.func(args) 266 | 267 | sys.exit(return_code) 268 | 269 | if __name__ == "__main__": 270 | main() 271 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.11, <4" 4 | 5 | [[package]] 6 | name = "argcomplete" 7 | version = "3.6.2" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, 12 | ] 13 | 14 | [[package]] 15 | name = "asttokens" 16 | version = "3.0.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } 19 | wheels = [ 20 | { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, 21 | ] 22 | 23 | [[package]] 24 | name = "colorama" 25 | version = "0.4.6" 26 | source = { registry = "https://pypi.org/simple" } 27 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 28 | wheels = [ 29 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 30 | ] 31 | 32 | [[package]] 33 | name = "executing" 34 | version = "2.2.1" 35 | source = { registry = "https://pypi.org/simple" } 36 | sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } 37 | wheels = [ 38 | { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, 39 | ] 40 | 41 | [[package]] 42 | name = "flake8" 43 | version = "7.3.0" 44 | source = { registry = "https://pypi.org/simple" } 45 | dependencies = [ 46 | { name = "mccabe" }, 47 | { name = "pycodestyle" }, 48 | { name = "pyflakes" }, 49 | ] 50 | sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } 51 | wheels = [ 52 | { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, 53 | ] 54 | 55 | [[package]] 56 | name = "flake8-pyproject" 57 | version = "1.2.3" 58 | source = { registry = "https://pypi.org/simple" } 59 | dependencies = [ 60 | { name = "flake8" }, 61 | ] 62 | wheels = [ 63 | { url = "https://files.pythonhosted.org/packages/5f/1d/635e86f9f3a96b7ea9e9f19b5efe17a987e765c39ca496e4a893bb999112/flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a", size = 4756, upload-time = "2023-03-21T20:51:38.911Z" }, 64 | ] 65 | 66 | [[package]] 67 | name = "icecream" 68 | version = "2.1.7" 69 | source = { registry = "https://pypi.org/simple" } 70 | dependencies = [ 71 | { name = "asttokens" }, 72 | { name = "colorama" }, 73 | { name = "executing" }, 74 | { name = "pygments" }, 75 | ] 76 | sdist = { url = "https://files.pythonhosted.org/packages/a2/d0/738341f0a3636a8e4bc798b98b9e0594d0de894e85208af43a6a05b9db44/icecream-2.1.7.tar.gz", hash = "sha256:936df053e65ff3c7a219fa79311b32bdae19aad83dd2e23d153ab8864b78acd9", size = 17355, upload-time = "2025-08-17T16:02:12.342Z" } 77 | wheels = [ 78 | { url = "https://files.pythonhosted.org/packages/7b/ae/7abd8d0db31ffafdc5088db7297d6fa14de53cb7daf6da21b4794a7567ce/icecream-2.1.7-py3-none-any.whl", hash = "sha256:b52cdd40cb5080279c4534fcd77aa510b1e7397a687e9d4daf2f8d2028bd738c", size = 14942, upload-time = "2025-08-17T16:02:11.468Z" }, 79 | ] 80 | 81 | [[package]] 82 | name = "mccabe" 83 | version = "0.7.0" 84 | source = { registry = "https://pypi.org/simple" } 85 | sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } 86 | wheels = [ 87 | { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, 88 | ] 89 | 90 | [[package]] 91 | name = "mypy" 92 | version = "1.17.1" 93 | source = { registry = "https://pypi.org/simple" } 94 | dependencies = [ 95 | { name = "mypy-extensions" }, 96 | { name = "pathspec" }, 97 | { name = "typing-extensions" }, 98 | ] 99 | sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } 100 | wheels = [ 101 | { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, 102 | { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, 103 | { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, 104 | { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, 105 | { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, 106 | { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, 107 | { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, 108 | { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, 109 | { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, 110 | { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, 111 | { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, 112 | { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, 113 | { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, 114 | { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, 115 | { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, 116 | { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, 117 | { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, 118 | { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, 119 | { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, 120 | { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, 121 | { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, 122 | { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, 123 | { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, 124 | { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, 125 | { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, 126 | ] 127 | 128 | [[package]] 129 | name = "mypy-extensions" 130 | version = "1.1.0" 131 | source = { registry = "https://pypi.org/simple" } 132 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } 133 | wheels = [ 134 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, 135 | ] 136 | 137 | [[package]] 138 | name = "nodeenv" 139 | version = "1.9.1" 140 | source = { registry = "https://pypi.org/simple" } 141 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } 142 | wheels = [ 143 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, 144 | ] 145 | 146 | [[package]] 147 | name = "pathspec" 148 | version = "0.12.1" 149 | source = { registry = "https://pypi.org/simple" } 150 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } 151 | wheels = [ 152 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, 153 | ] 154 | 155 | [[package]] 156 | name = "pycodestyle" 157 | version = "2.14.0" 158 | source = { registry = "https://pypi.org/simple" } 159 | sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } 160 | wheels = [ 161 | { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, 162 | ] 163 | 164 | [[package]] 165 | name = "pyflakes" 166 | version = "3.4.0" 167 | source = { registry = "https://pypi.org/simple" } 168 | sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } 169 | wheels = [ 170 | { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, 171 | ] 172 | 173 | [[package]] 174 | name = "pygments" 175 | version = "2.19.2" 176 | source = { registry = "https://pypi.org/simple" } 177 | sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } 178 | wheels = [ 179 | { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, 180 | ] 181 | 182 | [[package]] 183 | name = "pyright" 184 | version = "1.1.405" 185 | source = { registry = "https://pypi.org/simple" } 186 | dependencies = [ 187 | { name = "nodeenv" }, 188 | { name = "typing-extensions" }, 189 | ] 190 | sdist = { url = "https://files.pythonhosted.org/packages/fb/6c/ba4bbee22e76af700ea593a1d8701e3225080956753bee9750dcc25e2649/pyright-1.1.405.tar.gz", hash = "sha256:5c2a30e1037af27eb463a1cc0b9f6d65fec48478ccf092c1ac28385a15c55763", size = 4068319, upload-time = "2025-09-04T03:37:06.776Z" } 191 | wheels = [ 192 | { url = "https://files.pythonhosted.org/packages/d5/1a/524f832e1ff1962a22a1accc775ca7b143ba2e9f5924bb6749dce566784a/pyright-1.1.405-py3-none-any.whl", hash = "sha256:a2cb13700b5508ce8e5d4546034cb7ea4aedb60215c6c33f56cec7f53996035a", size = 5905038, upload-time = "2025-09-04T03:37:04.913Z" }, 193 | ] 194 | 195 | [[package]] 196 | name = "ruff" 197 | version = "0.12.12" 198 | source = { registry = "https://pypi.org/simple" } 199 | sdist = { url = "https://files.pythonhosted.org/packages/a8/f0/e0965dd709b8cabe6356811c0ee8c096806bb57d20b5019eb4e48a117410/ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6", size = 5359915, upload-time = "2025-09-04T16:50:18.273Z" } 200 | wheels = [ 201 | { url = "https://files.pythonhosted.org/packages/09/79/8d3d687224d88367b51c7974cec1040c4b015772bfbeffac95face14c04a/ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc", size = 12116602, upload-time = "2025-09-04T16:49:18.892Z" }, 202 | { url = "https://files.pythonhosted.org/packages/c3/c3/6e599657fe192462f94861a09aae935b869aea8a1da07f47d6eae471397c/ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727", size = 12868393, upload-time = "2025-09-04T16:49:23.043Z" }, 203 | { url = "https://files.pythonhosted.org/packages/e8/d2/9e3e40d399abc95336b1843f52fc0daaceb672d0e3c9290a28ff1a96f79d/ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb", size = 12036967, upload-time = "2025-09-04T16:49:26.04Z" }, 204 | { url = "https://files.pythonhosted.org/packages/e9/03/6816b2ed08836be272e87107d905f0908be5b4a40c14bfc91043e76631b8/ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577", size = 12276038, upload-time = "2025-09-04T16:49:29.056Z" }, 205 | { url = "https://files.pythonhosted.org/packages/9f/d5/707b92a61310edf358a389477eabd8af68f375c0ef858194be97ca5b6069/ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e", size = 11901110, upload-time = "2025-09-04T16:49:32.07Z" }, 206 | { url = "https://files.pythonhosted.org/packages/9d/3d/f8b1038f4b9822e26ec3d5b49cf2bc313e3c1564cceb4c1a42820bf74853/ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e", size = 13668352, upload-time = "2025-09-04T16:49:35.148Z" }, 207 | { url = "https://files.pythonhosted.org/packages/98/0e/91421368ae6c4f3765dd41a150f760c5f725516028a6be30e58255e3c668/ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8", size = 14638365, upload-time = "2025-09-04T16:49:38.892Z" }, 208 | { url = "https://files.pythonhosted.org/packages/74/5d/88f3f06a142f58ecc8ecb0c2fe0b82343e2a2b04dcd098809f717cf74b6c/ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5", size = 14060812, upload-time = "2025-09-04T16:49:42.732Z" }, 209 | { url = "https://files.pythonhosted.org/packages/13/fc/8962e7ddd2e81863d5c92400820f650b86f97ff919c59836fbc4c1a6d84c/ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92", size = 13050208, upload-time = "2025-09-04T16:49:46.434Z" }, 210 | { url = "https://files.pythonhosted.org/packages/53/06/8deb52d48a9a624fd37390555d9589e719eac568c020b27e96eed671f25f/ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45", size = 13311444, upload-time = "2025-09-04T16:49:49.931Z" }, 211 | { url = "https://files.pythonhosted.org/packages/2a/81/de5a29af7eb8f341f8140867ffb93f82e4fde7256dadee79016ac87c2716/ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5", size = 13279474, upload-time = "2025-09-04T16:49:53.465Z" }, 212 | { url = "https://files.pythonhosted.org/packages/7f/14/d9577fdeaf791737ada1b4f5c6b59c21c3326f3f683229096cccd7674e0c/ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4", size = 12070204, upload-time = "2025-09-04T16:49:56.882Z" }, 213 | { url = "https://files.pythonhosted.org/packages/77/04/a910078284b47fad54506dc0af13839c418ff704e341c176f64e1127e461/ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23", size = 11880347, upload-time = "2025-09-04T16:49:59.729Z" }, 214 | { url = "https://files.pythonhosted.org/packages/df/58/30185fcb0e89f05e7ea82e5817b47798f7fa7179863f9d9ba6fd4fe1b098/ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489", size = 12891844, upload-time = "2025-09-04T16:50:02.591Z" }, 215 | { url = "https://files.pythonhosted.org/packages/21/9c/28a8dacce4855e6703dcb8cdf6c1705d0b23dd01d60150786cd55aa93b16/ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee", size = 13360687, upload-time = "2025-09-04T16:50:05.8Z" }, 216 | { url = "https://files.pythonhosted.org/packages/c8/fa/05b6428a008e60f79546c943e54068316f32ec8ab5c4f73e4563934fbdc7/ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1", size = 12052870, upload-time = "2025-09-04T16:50:09.121Z" }, 217 | { url = "https://files.pythonhosted.org/packages/85/60/d1e335417804df452589271818749d061b22772b87efda88354cf35cdb7a/ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d", size = 13178016, upload-time = "2025-09-04T16:50:12.559Z" }, 218 | { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" }, 219 | ] 220 | 221 | [[package]] 222 | name = "typing-extensions" 223 | version = "4.15.0" 224 | source = { registry = "https://pypi.org/simple" } 225 | sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } 226 | wheels = [ 227 | { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, 228 | ] 229 | 230 | [[package]] 231 | name = "xcp-ng-dev" 232 | source = { editable = "." } 233 | dependencies = [ 234 | { name = "argcomplete" }, 235 | ] 236 | 237 | [package.dev-dependencies] 238 | dev = [ 239 | { name = "flake8" }, 240 | { name = "flake8-pyproject" }, 241 | { name = "icecream" }, 242 | { name = "mypy" }, 243 | { name = "pyright" }, 244 | { name = "ruff" }, 245 | { name = "typing-extensions" }, 246 | ] 247 | 248 | [package.metadata] 249 | requires-dist = [{ name = "argcomplete" }] 250 | 251 | [package.metadata.requires-dev] 252 | dev = [ 253 | { name = "flake8" }, 254 | { name = "flake8-pyproject" }, 255 | { name = "icecream" }, 256 | { name = "mypy" }, 257 | { name = "pyright" }, 258 | { name = "ruff" }, 259 | { name = "typing-extensions" }, 260 | ] 261 | --------------------------------------------------------------------------------