From 5e8bdf5105721f25c73499b4f84fca9f0c4a395b Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Fri, 30 Oct 2026 08:20:13 +0100 Subject: [PATCH] deb: build the packages from debian/ with build-deb-action debian/ becomes a Debian source package: the Build-Depends in control, a rules file with dh, a changelog and source/format. jtdor/build-deb-action installs the Build-Depends in a container of each release and runs dpkg-buildpackage. That replaces Dockerfile.linux, docker/build.sh and the buildx, cache and retry steps of the workflow, and dh computes the dependencies of the package, so nothing patches the control file or runs dpkg-shlibdeps by hand any more. debian/changelog does not repeat the version: debian/set-version.sh adds the entry for the version of pyproject.toml and the release, and the package is still named python3-monero_0.9.10-1noble1_amd64.deb. bin/build_monero_cpp.sh builds monero-project and monero-cpp, and debian/install-static-deps.sh the static expat and unbound, as the Dockerfile did. The flags of dpkg-buildflags are not passed to them. The packages are built on: - pull requests: amd64 on trixie and bookworm. - main: also amd64 on noble and jammy, and arm64 on trixie and bookworm. - tags and manual runs with full: the three architectures on the four releases. release.yml checks the tag and the runs of main that it needs first, then calls build-deb.yml and takes the packages from its own run, so a missing run does not show up after the hours of the build. arm64 and armhf are built natively on ubuntu-24.04-arm runners, which run 32 bit arm containers, instead of under qemu, where they took 76 to 136 minutes against 10 to 14 for amd64. A 32 bit container on a 64 bit arm kernel reports aarch64, which build_monero_cpp.sh would take for a 64 bit build and the configure scripts for the host, so debian/rules runs the steps of the build under linux32 there and the script knows armv7l and armv8l. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/build-deb.yml | 143 +++++++++---------------------- .github/workflows/release.yml | 42 ++++++--- .gitignore | 8 ++ Dockerfile.linux | 46 ---------- README.md | 28 +++--- bin/build_monero_cpp.sh | 48 +++++++++++ debian/DEBIAN/changelog | 4 - debian/DEBIAN/compat | 1 - debian/DEBIAN/control | 14 --- debian/DEBIAN/copyright | 6 -- debian/DEBIAN/rules | 4 - debian/changelog | 5 ++ debian/control | 51 ++++++++++- debian/copyright | 26 ++++++ debian/install-static-deps.sh | 33 +++++++ debian/rules | 45 ++++++++++ debian/set-version.sh | 32 +++++++ debian/{DEBIAN => source}/format | 0 docker/build.sh | 123 -------------------------- docs/ci-ccache.md | 8 +- pyproject.toml | 2 +- 21 files changed, 335 insertions(+), 334 deletions(-) delete mode 100644 Dockerfile.linux create mode 100755 bin/build_monero_cpp.sh delete mode 100644 debian/DEBIAN/changelog delete mode 100644 debian/DEBIAN/compat delete mode 100644 debian/DEBIAN/control delete mode 100644 debian/DEBIAN/copyright delete mode 100755 debian/DEBIAN/rules create mode 100644 debian/changelog create mode 100644 debian/copyright create mode 100755 debian/install-static-deps.sh create mode 100755 debian/rules create mode 100755 debian/set-version.sh rename debian/{DEBIAN => source}/format (100%) delete mode 100755 docker/build.sh diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml index d4d29a8..314fc7c 100644 --- a/.github/workflows/build-deb.yml +++ b/.github/workflows/build-deb.yml @@ -1,5 +1,10 @@ name: Build Debian Packages +# Builds the package from debian/ with dpkg-buildpackage in a container of each release. +# Pull requests build amd64 on trixie and bookworm. Main also builds amd64 on noble and jammy +# and arm64 on trixie and bookworm. The release (called by release.yml) and the manual runs with +# full build all of them. arm64 and armhf are built natively on arm runners, which run 32 bit +# arm containers. on: push: branches: [ main ] @@ -8,10 +13,19 @@ on: paths: - '.github/workflows/build-deb.yml' - 'debian/**' - - 'docker/**' - - 'Dockerfile.linux' + - 'bin/build_monero_cpp.sh' workflow_dispatch: + inputs: + full: + description: Build every release and architecture + type: boolean + default: false workflow_call: + inputs: + full: + description: Build every release and architecture + type: boolean + default: false # A new push to a pull request cancels the previous run. Other events get their own # group so they are never cancelled or replaced. @@ -25,61 +39,18 @@ permissions: jobs: build: - name: ${{ matrix.distro_name }} (${{ matrix.name }} / ${{ matrix.arch }}) - runs-on: ubuntu-latest + name: ${{ contains('noble jammy', matrix.name) && 'ubuntu' || 'debian' }} (${{ matrix.name }} / ${{ matrix.arch }}) + runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }} + timeout-minutes: 360 strategy: fail-fast: false matrix: - include: - - name: noble - distro_name: ubuntu - base_image: ubuntu:24.04 - arch: amd64 - - name: noble - distro_name: ubuntu - base_image: ubuntu:24.04 - arch: arm64 - - name: noble - distro_name: ubuntu - base_image: ubuntu:24.04 - arch: armhf - - name: jammy - distro_name: ubuntu - base_image: ubuntu:22.04 - arch: amd64 - - name: jammy - distro_name: ubuntu - base_image: ubuntu:22.04 - arch: arm64 - - name: jammy - distro_name: ubuntu - base_image: ubuntu:22.04 - arch: armhf - - name: trixie - distro_name: debian - base_image: debian:trixie - arch: amd64 - - name: trixie - distro_name: debian - base_image: debian:trixie - arch: arm64 - - name: trixie - distro_name: debian - base_image: debian:trixie - arch: armhf - - name: bookworm - distro_name: debian - base_image: debian:bookworm - arch: amd64 - - name: bookworm - distro_name: debian - base_image: debian:bookworm - arch: arm64 - - name: bookworm - distro_name: debian - base_image: debian:bookworm - arch: armhf + name: [noble, jammy, trixie, bookworm] + arch: [amd64, arm64, armhf] + # pull requests build amd64 on trixie and bookworm, main adds amd64 on the Ubuntu releases and + # arm64 on the Debian ones, the tags and the manual runs with full build everything + exclude: ${{ fromJSON((github.ref_type == 'tag' || inputs.full) && '[]' || (github.event_name == 'pull_request' && '[{"arch":"armhf"},{"arch":"arm64"},{"name":"noble"},{"name":"jammy"}]' || '[{"arch":"armhf"},{"arch":"arm64","name":"noble"},{"arch":"arm64","name":"jammy"}]')) }} steps: - name: Checkout repository @@ -90,43 +61,14 @@ jobs: - name: Determine package version id: version run: | - VERSION=$(grep -m1 '^version' pyproject.toml | sed -E 's/version = "(.*)"/\1/') + VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' pyproject.toml | head -n 1) echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - name: Setup qemu - if: matrix.arch == 'arm64' || matrix.arch == 'armhf' - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - - - name: Setup docker buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - - name: Free up disk space run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc || true docker system prune -af || true - - name: Prepare build environment - uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 - with: - timeout_minutes: 60 - max_attempts: 3 - retry_wait_seconds: 15 - command: | - PLATFORM=${{ matrix.arch == 'amd64' && 'linux/amd64' || - matrix.arch == 'armhf' && 'linux/arm/v7' || - 'linux/arm64' }} - - docker buildx build \ - --platform $PLATFORM \ - --build-arg BASE_IMAGE=${{ matrix.base_image }} \ - --build-arg THREADS=3 \ - -t monero-python:${{ matrix.name }}-${{ matrix.arch }} \ - -f Dockerfile.linux \ - --cache-from type=gha,scope=${{ matrix.name }}-${{ matrix.arch }} \ - --cache-to type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.arch }} \ - --load \ - . - - name: Restore ccache uses: actions/cache/restore@v4 with: @@ -135,26 +77,21 @@ jobs: restore-keys: | ccache-deb-${{ matrix.name }}-${{ matrix.arch }}_ - - name: Build monero-python - uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 + # Installs the Build-Depends of debian/control in the container, sets the version of + # debian/changelog from pyproject.toml and runs dpkg-buildpackage. The workspace is + # mounted at /github/workspace in the container. + - name: Build package + uses: jtdor/build-deb-action@473136f14a42a231124c1f7cefaedfe941203c38 # v1.9.1 + env: + CCACHE_DIR: /github/workspace/.ccache + CCACHE_MAXSIZE: 500M with: - timeout_minutes: 240 - max_attempts: 3 - retry_wait_seconds: 15 - command: | - PLATFORM=${{ matrix.arch == 'amd64' && 'linux/amd64' || - matrix.arch == 'armhf' && 'linux/arm/v7' || - 'linux/arm64' }} - PACKAGE_NAME="python3-monero_${{ steps.version.outputs.version }}-1${{ matrix.name }}1_${{ matrix.arch }}" - - docker run --rm \ - --platform $PLATFORM \ - -v $PWD:/monero-python -w /monero-python \ - -e PACKAGE_NAME="${PACKAGE_NAME}" \ - -e CCACHE_DIR=/monero-python/.ccache \ - -e CCACHE_MAXSIZE=500M \ - monero-python:${{ matrix.name }}-${{ matrix.arch }} \ - bash -euxc "./docker/build.sh" + # the image is not in the matrix: an include entry of a release left out by exclude + # would be a job of its own + docker-image: ${{ fromJSON('{"noble":"ubuntu:24.04","jammy":"ubuntu:22.04","trixie":"debian:trixie-slim","bookworm":"debian:bookworm-slim"}')[matrix.name] }} + extra-docker-args: --platform ${{ matrix.arch == 'armhf' && 'linux/arm/v7' || format('linux/{0}', matrix.arch) }} + before-build-hook: sh debian/set-version.sh ${{ matrix.name }} + buildpackage-opts: --build=binary --no-sign --jobs=auto - name: Save ccache # Pull requests only read the cache, pushes to main and manual runs replace it. @@ -167,4 +104,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: python3-monero_${{ steps.version.outputs.version }}-1${{ matrix.name }}1_${{ matrix.arch }} - path: build/*.deb + path: debian/artifacts/python3-monero_*.deb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1f5fa9..2ea7677 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,12 +10,14 @@ permissions: actions: read jobs: - release: - name: Publish draft release + # the macos and windows builds and the tests are those of main, the deb ones are built here + check: + name: Check the tag and the builds of main runs-on: ubuntu-latest - permissions: - contents: write - actions: read + outputs: + build_macos_run_id: ${{ steps.find_runs.outputs.build_macos_run_id }} + build_windows_run_id: ${{ steps.find_runs.outputs.build_windows_run_id }} + test_run_id: ${{ steps.find_runs.outputs.test_run_id }} steps: - name: Check the tag against the version in pyproject.toml @@ -40,7 +42,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - for wf in build-deb.yml build-macos.yml build-windows.yml test.yml; do + for wf in build-macos.yml build-windows.yml test.yml; do key=$(echo "${wf%.*}" | tr '-' '_') run_id=$(gh run list --repo "${{ github.repository }}" --workflow "$wf" \ --branch main --status success --json databaseId,headSha \ @@ -52,11 +54,29 @@ jobs: echo "${key}_run_id=${run_id}" >> "$GITHUB_OUTPUT" done + # every release and architecture, the builds of main only have some of them + deb: + name: Build the deb packages + needs: check + uses: ./.github/workflows/build-deb.yml + permissions: + contents: read + actions: write # build-deb.yml replaces its ccache entries + with: + full: true + + release: + name: Publish draft release + needs: [check, deb] + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + + steps: - name: Download deb packages uses: actions/download-artifact@v4 with: - github-token: ${{ github.token }} - run-id: ${{ steps.find_runs.outputs.build_deb_run_id }} pattern: python3-monero_* path: release-assets merge-multiple: true @@ -65,7 +85,7 @@ jobs: uses: actions/download-artifact@v4 with: github-token: ${{ github.token }} - run-id: ${{ steps.find_runs.outputs.build_macos_run_id }} + run-id: ${{ needs.check.outputs.build_macos_run_id }} pattern: monero-macos-* path: release-assets merge-multiple: true @@ -74,7 +94,7 @@ jobs: uses: actions/download-artifact@v4 with: github-token: ${{ github.token }} - run-id: ${{ steps.find_runs.outputs.build_windows_run_id }} + run-id: ${{ needs.check.outputs.build_windows_run_id }} name: monero-python-win-amd64 path: release-assets merge-multiple: true @@ -83,7 +103,7 @@ jobs: uses: actions/download-artifact@v4 with: github-token: ${{ github.token }} - run-id: ${{ steps.find_runs.outputs.test_run_id }} + run-id: ${{ needs.check.outputs.test_run_id }} name: coverage-reports-linux path: coverage-report diff --git a/.gitignore b/.gitignore index 3b6f043..3bb05b8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,11 @@ coverage* monero.log monero_tests_*.log .ccache +# left by dpkg-buildpackage +debian/artifacts +debian/python3-monero +debian/.debhelper +debian/debhelper-build-stamp +debian/files +debian/*.substvars +debian/*.log diff --git a/Dockerfile.linux b/Dockerfile.linux deleted file mode 100644 index 30166ad..0000000 --- a/Dockerfile.linux +++ /dev/null @@ -1,46 +0,0 @@ -ARG BASE_IMAGE=ubuntu:24.04 - -FROM ${BASE_IMAGE} - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y \ - build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev \ - libunwind8-dev liblzma-dev libreadline6-dev libexpat1-dev libpgm-dev \ - qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev \ - libprotobuf-dev protobuf-compiler libudev-dev libzstd-dev \ - libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev \ - libboost-locale-dev libboost-program-options-dev libboost-regex-dev \ - libboost-serialization-dev libboost-system-dev libboost-thread-dev \ - python3 python3-pip python3-all python3-pybind11 python3-pytest python3-pytest-rerunfailures \ - ccache doxygen graphviz git curl autoconf libtool gperf nettle-dev libevent-dev \ - debhelper bison flex wget \ - && ldconfig \ - && rm -rf /var/lib/apt/lists/* - -RUN . /etc/os-release && \ - if [ "$VERSION_CODENAME" = "jammy" ]; then \ - pip3 install scikit-build-core; \ - else \ - pip3 install scikit-build-core --break-system-packages; \ - fi - -RUN wget https://github.com/libexpat/libexpat/releases/download/R_2_7_1/expat-2.7.1.tar.bz2 \ - && echo "45c98ae1e9b5127325d25186cf8c511fa814078e9efeae7987a574b482b79b3d expat-2.7.1.tar.bz2" | sha256sum -c \ - && tar -xf expat-2.7.1.tar.bz2 \ - && cd expat-2.7.1 \ - && ./configure --enable-static --disable-shared --with-pic \ - && make -j$(nproc) && make install \ - && cd .. && rm -rf expat-2.7.1* - -RUN wget https://www.nlnetlabs.nl/downloads/unbound/unbound-1.22.0.tar.gz \ - && echo "c5dd1bdef5d5685b2cedb749158dd152c52d44f65529a34ac15cd88d4b1b3d43 unbound-1.22.0.tar.gz" | sha256sum -c \ - && tar xzf unbound-1.22.0.tar.gz \ - && cd unbound-1.22.0 \ - && ./configure --with-libexpat=/usr/local --with-ssl=/usr --enable-static --disable-shared --with-pic \ - && make -j$(nproc) && make install \ - && cd .. && rm -rf unbound-1.22.0* - -RUN git config --global --add safe.directory '*' - -WORKDIR /monero-python diff --git a/README.md b/README.md index cb07620..dfcd504 100644 --- a/README.md +++ b/README.md @@ -146,32 +146,30 @@ On anything else, build it from source. > **Faster rebuilds:** if [ccache](https://ccache.dev/) is installed it is used automatically for monero and monero-python (pass `-DUSE_CCACHE=OFF` to cmake to disable it). To use it for monero-cpp as well, build that with `-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache`. -### Linux Docker Build +### Debian package + +The package is built from [debian/](debian) with `dpkg-buildpackage`, the way CI does it. It installs the build dependencies and some static libraries system wide, so build it in a container of the release you want it for (`ubuntu:24.04`, `ubuntu:22.04`, `debian:trixie` or `debian:bookworm`). 1. Install [Docker](https://docs.docker.com/engine/install/) 2. Clone the repository ```sh git clone --recursive https://github.com/libmonero/monero-python.git ``` -3. Prepare build environment - ```sh - cd monero-python - docker build --tag monero-python:build-linux --file Dockerfile.linux . - ``` -4. (Optional) Specify build image - ```sh - cd monero-python - docker build --tag monero-python:build-linux --build-arg BASE_IMAGE="ubuntu:22.04" --file Dockerfile.linux . - ``` - -5. Build +3. Build, replacing `ubuntu:24.04` with the release you want ```sh - docker run --rm -it -v :/monero-python -w /monero-python monero-python:build-linux bash ./docker/build.sh + docker run --rm -it -v :/monero-python -w /monero-python ubuntu:24.04 bash -c ' + apt-get update && apt-get install -y dpkg-dev && + apt-get build-dep -y ./ && + debian/set-version.sh && + dpkg-buildpackage --build=binary --no-sign && + mkdir -p debian/artifacts && mv ../python3-monero_* debian/artifacts/' ``` * `MONERO_PYTHON_DIR_FULL_PATH` - full path to `monero-python` directory + * `debian/set-version.sh` adds an entry for the version of `pyproject.toml` to `debian/changelog`, there is no need to commit it + * add `--jobs=4` to `dpkg-buildpackage` to build faster, each compiler needs 1 to 2 GB of memory -6. Library build will be placed in `monero-python/build` directory +4. The package is placed in `monero-python/debian/artifacts` ### Windows diff --git a/bin/build_monero_cpp.sh b/bin/build_monero_cpp.sh new file mode 100755 index 0000000..0c07a32 --- /dev/null +++ b/bin/build_monero_cpp.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Builds monero-project and monero-cpp, the static libraries the python module links to. +# CI and the deb package use it on Linux and macOS. Windows has its own toolchain, +# see .github/workflows/build-windows.yml. +# +# JOBS parallel jobs for monero-project, 3 by default +# COVERAGE ON builds monero-cpp with coverage +# OPENSSL_ROOT_DIR the openssl both projects are built with, they look for brew's otherwise on macOS +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +jobs=${JOBS:-3} + +# monero builds with -march=native unless it is told which cpu to build for, and objects compiled +# on a machine with newer instructions, restored by ccache or shipped in a package, end in an illegal +# instruction on another cpu +case $(uname -m) in + x86_64) arch=x86-64;; + aarch64|arm64) arch=armv8-a;; + armv7l|armv8l) arch=armv7-a;; + *) arch=native;; +esac + +openssl_args=() +if [ -n "${OPENSSL_ROOT_DIR:-}" ]; then + openssl_args+=(-DOPENSSL_ROOT_DIR="$OPENSSL_ROOT_DIR") +fi + +# monero-project picks ccache up on its own +cd "$root/external/monero-cpp/external/monero-project" +mkdir -p build/release +cd build/release +cmake -DSTATIC=ON -DARCH="$arch" -DBUILD_64=ON -DCMAKE_BUILD_TYPE=Release ${openssl_args[@]+"${openssl_args[@]}"} ../../ +make -j"$jobs" wallet cryptonote_protocol + +# monero-cpp has to be told to use it +cd "$root/external/monero-cpp" +mkdir -p build +cd build +cmake_args=(${openssl_args[@]+"${openssl_args[@]}"}) +if command -v ccache > /dev/null 2>&1; then + cmake_args+=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache) +fi +if [ "${COVERAGE:-OFF}" = ON ]; then + cmake_args+=(-DENABLE_COVERAGE=ON) +fi +cmake .. ${cmake_args[@]+"${cmake_args[@]}"} +cmake --build . diff --git a/debian/DEBIAN/changelog b/debian/DEBIAN/changelog deleted file mode 100644 index dca30e8..0000000 --- a/debian/DEBIAN/changelog +++ /dev/null @@ -1,4 +0,0 @@ -python3-monero (0.0.1-1) main; urgency=medium - - -- everoddandeven - diff --git a/debian/DEBIAN/compat b/debian/DEBIAN/compat deleted file mode 100644 index b4de394..0000000 --- a/debian/DEBIAN/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/debian/DEBIAN/control b/debian/DEBIAN/control deleted file mode 100644 index 156e921..0000000 --- a/debian/DEBIAN/control +++ /dev/null @@ -1,14 +0,0 @@ -Source: python3-monero -Section: python -Priority: optional -Maintainer: everoddandeven -Build-Depends: build-essential, cmake, pkg-config, debhelper, python3-all, libssl-dev, libzmq3-dev, libunbound-dev, libsodium-dev, libunwind8-dev, liblzma-dev, libreadline6-dev, libexpat1-dev, libpgm-dev, qttools5-dev-tools, libhidapi-dev, libusb-1.0-0-dev, libprotobuf-dev, protobuf-compiler, libudev-dev, libboost-chrono-dev, libboost-date-time-dev, libboost-filesystem-dev, libboost-locale-dev, libboost-program-options-dev, libboost-regex-dev libboost-serialization-dev, libboost-system-dev, libboost-thread-dev, python3, ccache, doxygen, graphviz, nettle-dev, libevent-dev, python3-pip, python3-pybind11, python3-pytest, pybind11-dev -Depends: ${shlibs:Depends} -Standards-Version: 4.5.0 -Homepage: https://github.com/everoddandeven/monero-python -Rules-Requires-Root: no -Package: python3-monero -Architecture: all -Description: Python bindings for monero-cpp - This package provides Python 3 bindings for the monero-cpp library using pybind11. -Version: @VERSION@ diff --git a/debian/DEBIAN/copyright b/debian/DEBIAN/copyright deleted file mode 100644 index 1398915..0000000 --- a/debian/DEBIAN/copyright +++ /dev/null @@ -1,6 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: monero-python -Source: https://github.com/everoddandeven/monero-python -Files: * -Copyright: 2025 everoddandeven -License: MIT diff --git a/debian/DEBIAN/rules b/debian/DEBIAN/rules deleted file mode 100755 index 050e29a..0000000 --- a/debian/DEBIAN/rules +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/make -f - -%: - dh $@ --buildsystem=cmake diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..bc7cd58 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +python3-monero (0.0.1-1) unstable; urgency=medium + + * Initial packaging. + + -- everoddandeven Mon, 05 May 2025 00:28:21 +0200 diff --git a/debian/control b/debian/control index 115b31a..982cd8c 100644 --- a/debian/control +++ b/debian/control @@ -2,10 +2,57 @@ Source: python3-monero Section: python Priority: optional Maintainer: everoddandeven -Standards-Version: 4.5.0 +Build-Depends: + debhelper-compat (= 13), + autoconf, + bison, + ca-certificates, + ccache, + cmake, + curl, + flex, + git, + gperf, + liblzma-dev, + libboost-chrono-dev, + libboost-date-time-dev, + libboost-filesystem-dev, + libboost-locale-dev, + libboost-program-options-dev, + libboost-regex-dev, + libboost-serialization-dev, + libboost-system-dev, + libboost-thread-dev, + libevent-dev, + libexpat1-dev, + libhidapi-dev, + libpgm-dev, + libprotobuf-dev, + libreadline-dev, + libsodium-dev, + libssl-dev, + libtool, + libudev-dev, + libunbound-dev, + libunwind8-dev, + libusb-1.0-0-dev, + libzmq3-dev, + libzstd-dev, + nettle-dev, + pkg-config, + protobuf-compiler, + python3-all, + python3-dev, + python3-pip, + python3-pybind11, + qttools5-dev-tools, + wget, +Standards-Version: 4.6.2 +Homepage: https://github.com/libmonero/monero-python +Rules-Requires-Root: no Package: python3-monero Architecture: any -Depends: ${shlibs:Depends} +Depends: ${shlibs:Depends}, ${misc:Depends} Description: Python bindings for monero-cpp This package provides Python 3 bindings for the monero-cpp library using pybind11. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..a3a27bf --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: monero-python +Source: https://github.com/libmonero/monero-python + +Files: * +Copyright: 2025 everoddandeven +License: MIT + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/install-static-deps.sh b/debian/install-static-deps.sh new file mode 100755 index 0000000..619e49a --- /dev/null +++ b/debian/install-static-deps.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Installs static expat and unbound in /usr/local, which the archive does not provide +# that way and monero links to. It needs the network and root, so it is meant for a +# build container or a CI runner. Safe to run again. +set -eu + +jobs=${JOBS:-1} + +# the objects of both go through ccache when it is there +if command -v ccache > /dev/null 2>&1; then + export CC="ccache gcc" +fi + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +cd "$work" + +if [ ! -f /usr/local/lib/libexpat.a ]; then + wget -nv https://github.com/libexpat/libexpat/releases/download/R_2_7_1/expat-2.7.1.tar.bz2 + echo "45c98ae1e9b5127325d25186cf8c511fa814078e9efeae7987a574b482b79b3d expat-2.7.1.tar.bz2" | sha256sum -c + tar -xf expat-2.7.1.tar.bz2 + (cd expat-2.7.1 && ./configure --enable-static --disable-shared --with-pic && make -j"$jobs" && make install) +fi + +if [ ! -f /usr/local/lib/libunbound.a ]; then + wget -nv https://www.nlnetlabs.nl/downloads/unbound/unbound-1.22.0.tar.gz + echo "c5dd1bdef5d5685b2cedb749158dd152c52d44f65529a34ac15cd88d4b1b3d43 unbound-1.22.0.tar.gz" | sha256sum -c + tar -xzf unbound-1.22.0.tar.gz + # monero only needs libunbound, skip the daemon and the tools + (cd unbound-1.22.0 && ./configure --with-libexpat=/usr/local --with-ssl=/usr --enable-static --disable-shared --with-pic && make -j"$jobs" lib && make install-lib) +fi + +ldconfig diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..9383ea6 --- /dev/null +++ b/debian/rules @@ -0,0 +1,45 @@ +#!/usr/bin/make -f + +include /usr/share/dpkg/buildopts.mk + +# the jobs given with dpkg-buildpackage -j, one by default +JOBS := $(or $(DEB_BUILD_OPTION_PARALLEL),1) +SITE_PACKAGES := debian/python3-monero/usr/lib/python3/dist-packages + +# a 32 bit arm container on a 64 bit arm runner still reports aarch64 and configure scripts and +# cmake go by that, linux32 makes it report armv8l +ifeq ($(shell dpkg --print-architecture)/$(shell uname -m),armhf/aarch64) +LINUX32 := linux32 +endif + +# dh exports the flags of dpkg-buildflags (hardening, -g, LTO on Ubuntu), monero and the +# module set their own and are built the same way outside of the package +unexport CFLAGS CPPFLAGS CXXFLAGS LDFLAGS FFLAGS FCFLAGS OBJCFLAGS OBJCXXFLAGS + +%: + dh $@ --buildsystem=none + +# static expat and unbound, and scikit-build-core, which is not in every release and +# which pip refuses to install next to the system packages on the newer ones +override_dh_auto_configure: + -ccache --zero-stats + $(LINUX32) env JOBS=$(JOBS) debian/install-static-deps.sh + python3 -c 'import scikit_build_core' || pip3 install scikit-build-core || pip3 install --break-system-packages scikit-build-core + +override_dh_auto_build: + $(LINUX32) env JOBS=$(JOBS) bin/build_monero_cpp.sh + +# the module is built here by scikit-build-core, which finds what the step above built +override_dh_auto_install: + $(LINUX32) pip3 install -vvv . --no-build-isolation --target $(SITE_PACKAGES) + cp -R src/python $(SITE_PACKAGES)/monero + rm -rf $(SITE_PACKAGES)/pybind11* $(SITE_PACKAGES)/bin + -ccache --show-stats + +# no package of debug symbols +override_dh_strip: + dh_strip --no-automatic-dbgsym + +# the module also links libraries that are not packaged +override_dh_shlibdeps: + dh_shlibdeps -- --ignore-missing-info diff --git a/debian/set-version.sh b/debian/set-version.sh new file mode 100755 index 0000000..1401b2b --- /dev/null +++ b/debian/set-version.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# Adds an entry to debian/changelog for the version declared in pyproject.toml, so the +# version is not repeated here. The package is named like python3-monero_0.9.10-1noble1_amd64.deb. +# +# usage: debian/set-version.sh [distribution] the distribution is the one of the system by default +set -eu + +cd "$(dirname "$0")/.." + +version=$(sed -n 's/^version = "\(.*\)"$/\1/p' pyproject.toml | head -n 1) +if [ -z "$version" ]; then + echo "ERROR: could not read the version from pyproject.toml" >&2 + exit 1 +fi + +dist=${1:-$(. /etc/os-release && echo "$VERSION_CODENAME")} +full_version="${version}-1${dist}1" + +if [ "$(dpkg-parsechangelog -S Version)" = "$full_version" ]; then + echo "debian/changelog is already at $full_version" + exit 0 +fi + +maintainer=$(sed -n 's/^Maintainer: //p' debian/control) +{ + printf 'python3-monero (%s) %s; urgency=medium\n\n' "$full_version" "$dist" + printf ' * Build of %s for %s.\n\n' "$version" "$dist" + printf ' -- %s %s\n\n' "$maintainer" "$(date -R)" + cat debian/changelog +} > debian/changelog.new +mv debian/changelog.new debian/changelog +echo "debian/changelog is now at $full_version" diff --git a/debian/DEBIAN/format b/debian/source/format similarity index 100% rename from debian/DEBIAN/format rename to debian/source/format diff --git a/docker/build.sh b/docker/build.sh deleted file mode 100755 index a23f4af..0000000 --- a/docker/build.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -set -e - -DISTRO_CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d'=' -f2) - -# the version is declared only in pyproject.toml -VERSION=$(sed -n 's/^version = "\(.*\)"$/\1/p' pyproject.toml | head -n 1) -if [ -z "$VERSION" ]; then - echo "ERROR: could not read the version from pyproject.toml" >&2 - exit 1 -fi - -if [ -z "$DISTRO_CODENAME" ]; then - DISTRO_CODENAME=$(lsb_release -c -s 2>/dev/null || echo "unknown") -fi - -ARCH=$(dpkg --print-architecture 2>/dev/null || uname -m) -if [ "$ARCH" == "x86_64" ]; then - ARCH="amd64" -elif [[ "$ARCH" == arm64* || "$ARCH" == aarch64* ]]; then - ARCH="arm64" -elif [[ "$ARCH" == armv7* || "$ARCH" == "armhf" ]]; then - ARCH="armhf" -fi - -PACKAGE_NAME=${PACKAGE_NAME:-python3-monero_${VERSION}-1${DISTRO_CODENAME}1_${ARCH}} - -echo "Building package: ${PACKAGE_NAME}" -echo "Distro codename: ${DISTRO_CODENAME}" -echo "Architecture: ${ARCH}" - -git submodule update --init --recursive -mkdir -p build - -# monero-project picks ccache up on its own, monero-cpp has to be told to use it -CCACHE_CMAKE_ARGS=() -if command -v ccache >/dev/null 2>&1; then - ccache --zero-stats - CCACHE_CMAKE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache) -fi - -# build monero-project dependencies -cd ./external/monero-cpp/external/monero-project/ -git submodule update --init --force -HOST_NCORES=$(nproc 2>/dev/null || shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) -if [[ $(uname -s) == "MINGW64_NT"* || $(uname -s) == "MSYS"* ]]; then - bit=$(getconf LONG_BIT) - if [ "$bit" == "64" ]; then - make release-static-win64 -j$HOST_NCORES - else - make release-static-win32 -j$HOST_NCORES - fi -else - # OS is not windows. monero builds with -march=native unless it is told which cpu to build for, and objects compiled - # on a runner with newer instructions, restored by ccache or shipped in a package, end in an illegal - # instruction on another cpu - case $(uname -m) in - x86_64) MONERO_ARCH="x86-64";; - aarch64|arm64) MONERO_ARCH="armv8-a";; - *) MONERO_ARCH="native";; - esac - mkdir -p build/release && - cd build/release && - cmake -DSTATIC=ON -DARCH="$MONERO_ARCH" -DBUILD_64=ON -DCMAKE_BUILD_TYPE=Release ../../ - make -j$HOST_NCORES wallet cryptonote_protocol -fi -cd ../../../../ - -# build libmonero-cpp library -mkdir -p build && -cd build && -cmake .. "${CCACHE_CMAKE_ARGS[@]}" && -cmake --build . && -make . -cd ../../../ - -pip3 install -vvv . --no-build-isolation --target build/${PACKAGE_NAME}/usr/lib/python3/dist-packages - -if command -v ccache >/dev/null 2>&1; then - ccache --show-stats -fi - -cp -R src/python build/${PACKAGE_NAME}/usr/lib/python3/dist-packages/monero -rm -rf build/${PACKAGE_NAME}/usr/lib/python3/dist-packages/pybind11* -rm -rf build/${PACKAGE_NAME}/usr/lib/python3/dist-packages/bin -cp -R debian/DEBIAN "build/${PACKAGE_NAME}/DEBIAN" - -CONTROL_FILE="build/${PACKAGE_NAME}/DEBIAN/control" -if [ -f "$CONTROL_FILE" ]; then - echo "Patching control file architecture to: ${ARCH}" - sed -i "s/^Architecture: .*/Architecture: ${ARCH}/" "$CONTROL_FILE" - - echo "Patching control file version to: ${VERSION}" - sed -i "s/^Version: .*/Version: ${VERSION}/" "$CONTROL_FILE" - - echo "Computing runtime library dependencies with dpkg-shlibdeps" - SO_FILES=$(find "build/${PACKAGE_NAME}/usr/lib/python3/dist-packages" -maxdepth 1 -name "*.so") - if [ -z "$SO_FILES" ]; then - echo "ERROR: no compiled .so found in package tree for shlibdeps analysis" >&2 - exit 1 - fi - - SHLIBS_OUT=$(mktemp) - dpkg-shlibdeps -O --ignore-missing-info $SO_FILES > "$SHLIBS_OUT" - SHLIBS_DEPENDS=$(sed -n 's/^shlibs:Depends=//p' "$SHLIBS_OUT") - rm -f "$SHLIBS_OUT" - - if [ -z "$SHLIBS_DEPENDS" ]; then - echo "ERROR: dpkg-shlibdeps produced no dependency information" >&2 - exit 1 - fi - - echo "Computed dependencies: ${SHLIBS_DEPENDS}" - CONTROL_CONTENT=$(cat "$CONTROL_FILE") - CONTROL_CONTENT="${CONTROL_CONTENT//\$\{shlibs:Depends\}/$SHLIBS_DEPENDS}" - printf '%s\n' "$CONTROL_CONTENT" > "$CONTROL_FILE" -else - echo "WARNING: control file not found at: $CONTROL_FILE" -fi - -dpkg-deb --build build/${PACKAGE_NAME} - -echo "Package built successfully: build/${PACKAGE_NAME}.deb" diff --git a/docs/ci-ccache.md b/docs/ci-ccache.md index 66769d2..bee2f97 100644 --- a/docs/ci-ccache.md +++ b/docs/ci-ccache.md @@ -5,7 +5,7 @@ The workflows keep a ccache directory (`.ccache` in the workspace) between runs ### How it's wired - monero-project already uses ccache on its own (`USE_CCACHE`, `FindCcache`). Nothing is passed to it. Don't set `CMAKE_CXX_COMPILER_LAUNCHER` in the environment for that step, it would be wrapped twice. -- monero-cpp is a submodule and doesn't know about ccache. The workflows and `docker/build.sh` pass `-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache` when configuring it. +- monero-cpp is a submodule and doesn't know about ccache. The workflows and `bin/build_monero_cpp.sh`, which the deb package uses, pass `-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache` when configuring it. - monero-python picks ccache up itself if it finds it. `-DUSE_CCACHE=OFF` turns that off. A launcher passed on the command line or in the environment wins. - `CCACHE_DIR` is `.ccache`, `CCACHE_MAXSIZE` is 500M per job. @@ -26,7 +26,7 @@ The workflows keep a ccache directory (`.ccache` in the workspace) between runs - macOS: `CCACHE_COMPILERCHECK` is `%compiler% -v`. The default hashes mtime and size of `/usr/bin/c++`, which is a shim and stays the same when Xcode updates, so you'd keep getting objects from an old clang. Costs one extra process per compile. - Windows: ccache comes from the rolling MSYS2 repo through `setup-msys2`, the toolchain is pinned afterwards with `pacman -U`. Since gcc 16.2.0-4 MSYS2 splits `gcc-libs` into `libgcc`, `libstdc++` and `cc-libs`, which conflict with the pinned `gcc-libs`, so the workflows remove them before the downgrade. ccache keeps working because the pinned `gcc-libs` provides `cc-libs`. `boost-libs` is pinned next to `boost` for the same reason. If ccache stops starting after you touch the pins, look there first. If it keeps being a problem, remove the ccache steps from the two Windows jobs, nothing else depends on them. -- deb: `.ccache` sits in the workspace that is mounted into the container, `CCACHE_DIR` is passed to `docker run`. Files end up owned by root on the host, the cache action only reads them. The runner has no ccache, so the stats are printed by `docker/build.sh`. Builds under qemu (arm64, armhf) are the slow ones and gain the most. +- deb: `build-deb-action` mounts the workspace at `/github/workspace` in the container, and `CCACHE_DIR` is set to `/github/workspace/.ccache` in the env of the step, which reaches the container. Files end up owned by root on the host, the cache action only reads them. The runner has no ccache, so the stats are printed by `debian/rules`. Static expat and unbound go through ccache too (`debian/install-static-deps.sh`). Builds under qemu (arm64, armhf) are the slow ones and gain the most. ### Quota @@ -36,7 +36,7 @@ The repo is at the limit. On 2026-10-29 it held 10.06 GB: 8.1 GB of buildx layer The ccache entries are small: about 29 MB on Windows, 25 MB on macOS, 18 MB for the Linux tests and 43 to 48 MB for each deb leg. 500M is never reached, so lowering it changes nothing. What takes space is that every push to main saves a new entry per job and the older ones stay until they are evicted, about 1 GB of the 1.7. Only the newest entry of a name is ever restored. -Not done: stop the pull requests that touch the deb files from writing the buildx cache (`--cache-to` has no pull request guard), and delete the previous entry after saving a new one. +The deb builds no longer use buildx, so nothing writes that cache any more. The existing entries stay until they are evicted, `gh cache list` shows them and `gh cache delete ` frees the space. Not done: delete the previous ccache entry after saving a new one. ### What it buys @@ -44,6 +44,6 @@ Measured on `build monero` for Windows: about 9 minutes with a cold cache, about ### Checking it works -Run `test.yml` twice on the same branch with `workflow_dispatch`. Second run should show mostly hits in the "Show ccache statistics" step (for deb it's the end of the `build.sh` output). Stats are zeroed at the start of each job, so they're only for that run. +Run `test.yml` twice on the same branch with `workflow_dispatch`. Second run should show mostly hits in the "Show ccache statistics" step (for deb it's the end of the build step, printed by `debian/rules`). Stats are zeroed at the start of each job, so they're only for that run. To start over: `gh cache list`, then `gh cache delete `. diff --git a/pyproject.toml b/pyproject.toml index 2342dff..7c1e37e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "monero" -# the only place where the version is declared: CMakeLists.txt, docker/build.sh and +# the only place where the version is declared: CMakeLists.txt, debian/set-version.sh and # the release workflow read it from here version = "0.9.10" description = "A Python library for using Monero"