mirror of
https://github.com/borgbackup/borg.git
synced 2026-09-01 14:13:19 +02:00
a6970c7e7 did this for the sdist: its provenance attestation is only
stored in the GitHub attestations API, and fetching it from there needs
a recent gh and a GitHub token, so the sigstore bundle is attached to
the release as <asset>.sigstore.jsonl for offline verification.
Do the same for the standalone binaries: the jobs that build them attest
them, so they are also the ones that have the bundle. Each of them stores
it under the name of every asset it attested (one attestation covers both
the single-file binary and the .tgz) and uploads it in the artifact that
carries the binaries, so the release job attaches it to the release along
with them - it uploads whatever is in the artifacts.
vm_tests attested after uploading its artifact, so the attestation is now
made before the upload, like in the other two jobs.
Also document the offline verification in the binaries README (which is a
release asset itself) and in the release checklist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1013 lines
42 KiB
YAML
1013 lines
42 KiB
YAML
# badge: https://github.com/borgbackup/borg/workflows/CI/badge.svg?branch=master
|
|
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [ master ]
|
|
tags:
|
|
- '2.*'
|
|
pull_request:
|
|
branches: [ master ]
|
|
paths:
|
|
- '**.py'
|
|
- '**.pyx'
|
|
- '**.c'
|
|
- '**.h'
|
|
- '**.yml'
|
|
- '**.toml'
|
|
- '**.cfg'
|
|
- '**.ini'
|
|
- 'requirements.d/*'
|
|
- '!docs/**'
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
# Force colored tox and pytest output even without a tty - the GitHub
|
|
# Actions log viewer renders ANSI colors. tox passes both vars through
|
|
# to pytest (pass_env = ["*"]).
|
|
PY_COLORS: "1" # pytest
|
|
TOX_COLORED: "yes" # tox's own output
|
|
|
|
jobs:
|
|
lint:
|
|
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 5
|
|
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
- uses: astral-sh/ruff-action@278981a28ce3188b1e39527901f38254bf3aac89 # v4.1.0
|
|
|
|
security:
|
|
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 5
|
|
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: '3.11'
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install bandit[toml]
|
|
- name: Run Bandit
|
|
run: |
|
|
bandit -r src/borg -c pyproject.toml
|
|
|
|
asan_ubsan:
|
|
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 25
|
|
needs: [lint]
|
|
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
# Just fetching one commit is not enough for setuptools-scm, so we fetch all.
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
persist-credentials: false
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: '3.12'
|
|
|
|
- name: Install system packages
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y pkg-config build-essential
|
|
sudo apt-get install -y libssl-dev libacl1-dev liblz4-dev
|
|
|
|
- name: Install Python dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements.d/development.lock.txt
|
|
|
|
- name: Build Borg with ASan/UBSan
|
|
# Build the C/Cython extensions with AddressSanitizer and UndefinedBehaviorSanitizer enabled.
|
|
# How this works:
|
|
# - The -fsanitize=address,undefined flags inject runtime checks into our native code. If a bug is hit
|
|
# (e.g., buffer overflow, use-after-free, out-of-bounds, or undefined behavior), the sanitizer prints
|
|
# a detailed error report to stderr, including a stack trace, and forces the process to exit with
|
|
# non-zero status. In CI, this will fail the step/job so you will notice.
|
|
# - ASAN_OPTIONS/UBSAN_OPTIONS configure the sanitizers' runtime behavior (see below for meanings).
|
|
env:
|
|
CFLAGS: "-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined"
|
|
CXXFLAGS: "-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined"
|
|
LDFLAGS: "-fsanitize=address,undefined"
|
|
# ASAN_OPTIONS controls AddressSanitizer runtime tweaks:
|
|
# - detect_leaks=0: Disable LeakSanitizer to avoid false positives with CPython/pymalloc in short-lived tests.
|
|
# - strict_string_checks=1: Make invalid string operations (e.g., over-reads) more likely to be detected.
|
|
# - check_initialization_order=1: Catch uses that depend on static initialization order (C++).
|
|
# - detect_stack_use_after_return=1: Detect stack-use-after-return via stack poisoning (may increase overhead).
|
|
ASAN_OPTIONS: "detect_leaks=0:strict_string_checks=1:check_initialization_order=1:detect_stack_use_after_return=1"
|
|
# UBSAN_OPTIONS controls UndefinedBehaviorSanitizer runtime:
|
|
# - print_stacktrace=1: Include a stack trace for UB reports to ease debugging.
|
|
# Note: UBSan is recoverable by default (process may continue after reporting). If you want CI to
|
|
# abort immediately and fail on the first UB, add `halt_on_error=1` (e.g., UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1").
|
|
UBSAN_OPTIONS: "print_stacktrace=1"
|
|
# PYTHONDEVMODE enables additional Python runtime checks and warnings.
|
|
PYTHONDEVMODE: "1"
|
|
run: pip install -e .
|
|
|
|
- name: Run tests under sanitizers
|
|
env:
|
|
ASAN_OPTIONS: "detect_leaks=0:strict_string_checks=1:check_initialization_order=1:detect_stack_use_after_return=1"
|
|
UBSAN_OPTIONS: "print_stacktrace=1"
|
|
PYTHONDEVMODE: "1"
|
|
# Ensure the ASan runtime is loaded first to avoid "ASan runtime does not come first" warnings.
|
|
# We discover libasan/libubsan paths via gcc and preload them for the Python test process.
|
|
# the remote tests are slow and likely won't find anything useful
|
|
run: |
|
|
set -euo pipefail
|
|
export LD_PRELOAD="$(gcc -print-file-name=libasan.so):$(gcc -print-file-name=libubsan.so)"
|
|
echo "Using LD_PRELOAD=$LD_PRELOAD"
|
|
# the sanitizer runtimes write their reports to stderr, so they still show up per worker
|
|
pytest -v -n auto --benchmark-skip -k "not remote"
|
|
|
|
native_tests:
|
|
|
|
needs: [lint]
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
attestations: write
|
|
strategy:
|
|
# entries with "allow-failure": true (e.g. prerelease pythons) do not fail
|
|
# the matrix, see continue-on-error below.
|
|
fail-fast: true
|
|
# noinspection YAMLSchemaValidation
|
|
# "coverage": "1" collects coverage in that entry - we only do that for the
|
|
# oldest and the newest supported python, measuring is not free, see #9470.
|
|
matrix: >-
|
|
${{ fromJSON(
|
|
github.event_name == 'pull_request' && '{
|
|
"include": [
|
|
{"os": "ubuntu-26.04", "python-version": "3.11", "toxenv": "mypy"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.11", "toxenv": "docs"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.11", "toxenv": "py311-llfuse", "coverage": "1"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.12", "toxenv": "py312-pyfuse3", "store_cache": "1"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-mfusepy", "coverage": "1"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.15-dev", "toxenv": "py315-mfusepy", "allow-failure": true}
|
|
]
|
|
}' || '{
|
|
"include": [
|
|
{"os": "ubuntu-26.04", "python-version": "3.11", "toxenv": "py311-llfuse", "coverage": "1"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.12", "toxenv": "py312-pyfuse3"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.13", "toxenv": "py313-mfusepy", "store_cache": "1"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.14", "toxenv": "py314-pyfuse3", "coverage": "1", "binary": "borg-linux-glibc243-x86_64-gh"},
|
|
{"os": "ubuntu-26.04-arm", "python-version": "3.14", "toxenv": "py314-pyfuse3", "coverage": "1", "binary": "borg-linux-glibc243-arm64-gh"},
|
|
{"os": "macos-15", "python-version": "3.14", "toxenv": "py314-none", "coverage": "1", "binary": "borg-macos-15-arm64-gh"},
|
|
{"os": "macos-15-intel", "python-version": "3.14", "toxenv": "py314-none", "coverage": "1", "binary": "borg-macos-15-x86_64-gh"},
|
|
{"os": "ubuntu-26.04", "python-version": "3.15-dev", "toxenv": "py315-mfusepy", "allow-failure": true}
|
|
]
|
|
}'
|
|
) }}
|
|
env:
|
|
TOXENV: ${{ matrix.toxenv }}
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
# Python 3.15 is not released yet, so do not fail the whole matrix if it (or a
|
|
# dependency not having wheels / not compiling for it yet) breaks.
|
|
# Remove the "allow-failure" matrix entries once 3.15 is final.
|
|
continue-on-error: ${{ matrix.allow-failure || false }}
|
|
# macOS machines can be slow, if overloaded.
|
|
timeout-minutes: 360
|
|
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
# Just fetching one commit is not enough for setuptools-scm, so we fetch all.
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
persist-credentials: false
|
|
|
|
- name: Set up Python ${{ matrix.python-version }}
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
|
|
- name: Cache pip
|
|
# Not on tags: that is when the release binaries are built and attested,
|
|
# and they should not be able to pick up a poisoned cache entry.
|
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ~/.cache/pip
|
|
key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ hashFiles('requirements.d/development.lock.txt') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-pip-
|
|
|
|
- name: Cache tox environments
|
|
# Not on tags: that is when the release binaries are built and attested,
|
|
# and they should not be able to pick up a poisoned cache entry.
|
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: .tox
|
|
key: ${{ runner.os }}-${{ runner.arch }}-tox-${{ matrix.toxenv }}-${{ hashFiles('requirements.d/development.lock.txt', 'pyproject.toml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-tox-${{ matrix.toxenv }}-
|
|
${{ runner.os }}-${{ runner.arch }}-tox-
|
|
|
|
- name: Install Linux packages
|
|
if: ${{ runner.os == 'Linux' }}
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y pkg-config build-essential
|
|
sudo apt-get install -y libssl-dev libacl1-dev liblz4-dev
|
|
sudo apt-get install -y bash zsh fish # for shell completion tests
|
|
sudo apt-get install -y rclone openssh-server curl
|
|
if [[ "$TOXENV" == *"llfuse"* ]]; then
|
|
sudo apt-get install -y libfuse-dev fuse # Required for Python llfuse module
|
|
elif [[ "$TOXENV" == *"pyfuse3"* || "$TOXENV" == *"mfusepy"* ]]; then
|
|
sudo apt-get install -y libfuse3-dev fuse3 # Required for Python pyfuse3 module
|
|
fi
|
|
|
|
- name: Install macOS packages
|
|
if: ${{ runner.os == 'macOS' }}
|
|
run: |
|
|
brew unlink pkg-config@0.29.2 || true
|
|
brew bundle install
|
|
# the runner's unresolvable .local hostname takes macOS ~35s to give up on,
|
|
# which borg paid per spawned process (import-time fqdn) - hours of test time,
|
|
# see #9470. .local is mDNS territory, an /etc/hosts entry alone does not help.
|
|
sudo scutil --set HostName borg-ci-mac
|
|
echo "127.0.0.1 borg-ci-mac" | sudo tee -a /etc/hosts
|
|
|
|
- name: Configure OpenSSH SFTP server (test only)
|
|
if: ${{ runner.os == 'Linux' && !contains(matrix.toxenv, 'mypy') && !contains(matrix.toxenv, 'docs') }}
|
|
run: |
|
|
sudo mkdir -p /run/sshd
|
|
sudo useradd -m -s /bin/bash sftpuser || true
|
|
# Create SSH key for the CI user and authorize it for sftpuser
|
|
mkdir -p ~/.ssh
|
|
chmod 700 ~/.ssh
|
|
test -f ~/.ssh/id_ed25519 || ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
|
|
sudo mkdir -p /home/sftpuser/.ssh
|
|
sudo chmod 700 /home/sftpuser/.ssh
|
|
sudo cp ~/.ssh/id_ed25519.pub /home/sftpuser/.ssh/authorized_keys
|
|
sudo chown -R sftpuser:sftpuser /home/sftpuser/.ssh
|
|
sudo chmod 600 /home/sftpuser/.ssh/authorized_keys
|
|
# Allow publickey auth and enable Subsystem sftp
|
|
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
|
|
sudo sed -i 's/^#\?PubkeyAuthentication .*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
|
|
if ! grep -q '^Subsystem sftp' /etc/ssh/sshd_config; then echo 'Subsystem sftp /usr/lib/openssh/sftp-server' | sudo tee -a /etc/ssh/sshd_config; fi
|
|
# Ensure host keys exist to avoid slow generation on first sshd start
|
|
sudo ssh-keygen -A
|
|
# Start sshd (listen on default 22 inside runner)
|
|
sudo /usr/sbin/sshd -D &
|
|
# Add host key to known_hosts so paramiko trusts it
|
|
ssh-keyscan -H localhost 127.0.0.1 | tee -a ~/.ssh/known_hosts
|
|
# Start ssh-agent and add our key so paramiko can use the agent
|
|
eval "$(ssh-agent -s)"
|
|
ssh-add ~/.ssh/id_ed25519
|
|
# The rest test starts "borg serve --rest" over ssh as sftpuser, which runs the borg
|
|
# under test from the tox venv under $HOME. Allow sftpuser to traverse into the runner
|
|
# home so it can reach that borg (the venv dirs/files are created world-r/x by tox/pip).
|
|
sudo chmod o+x "$HOME"
|
|
# Export SFTP test URL for tox via GITHUB_ENV
|
|
echo "BORG_TEST_SFTP_REPO=sftp://sftpuser@localhost:22/borg/sftp-repo" >> $GITHUB_ENV
|
|
echo "BORG_TEST_REST_REPO=rest://sftpuser@localhost:22/borg/rest-repo" >> $GITHUB_ENV
|
|
|
|
- name: Install and configure MinIO S3 server (test only)
|
|
if: ${{ runner.os == 'Linux' && !contains(matrix.toxenv, 'mypy') && !contains(matrix.toxenv, 'docs') }}
|
|
run: |
|
|
set -e
|
|
arch=$(uname -m)
|
|
case "$arch" in
|
|
x86_64|amd64) srv_url=https://dl.min.io/server/minio/release/linux-amd64/minio; cli_url=https://dl.min.io/client/mc/release/linux-amd64/mc ;;
|
|
aarch64|arm64) srv_url=https://dl.min.io/server/minio/release/linux-arm64/minio; cli_url=https://dl.min.io/client/mc/release/linux-arm64/mc ;;
|
|
*) echo "Unsupported arch: $arch"; exit 1 ;;
|
|
esac
|
|
curl -fsSL -o /usr/local/bin/minio "$srv_url"
|
|
curl -fsSL -o /usr/local/bin/mc "$cli_url"
|
|
sudo chmod +x /usr/local/bin/minio /usr/local/bin/mc
|
|
export PATH=/usr/local/bin:$PATH
|
|
# Start MinIO on :9000 with default credentials (minioadmin/minioadmin)
|
|
MINIO_DIR="$GITHUB_WORKSPACE/.minio-data"
|
|
MINIO_LOG="$GITHUB_WORKSPACE/.minio.log"
|
|
mkdir -p "$MINIO_DIR"
|
|
nohup minio server "$MINIO_DIR" --address ":9000" >"$MINIO_LOG" 2>&1 &
|
|
# Wait for MinIO port to be ready
|
|
for i in $(seq 1 60); do (echo > /dev/tcp/127.0.0.1/9000) >/dev/null 2>&1 && break; sleep 1; done
|
|
# Configure client and create bucket
|
|
mc alias set local http://127.0.0.1:9000 minioadmin minioadmin
|
|
mc mb --ignore-existing local/borg
|
|
# Export S3 test URL for tox via GITHUB_ENV
|
|
echo "BORG_TEST_S3_REPO=s3:minioadmin:minioadmin@http://127.0.0.1:9000/borg/s3-repo" >> $GITHUB_ENV
|
|
|
|
- name: Enable store cache (test only)
|
|
if: ${{ matrix.store_cache == '1' }}
|
|
run: echo "BORG_STORE_CACHE=1" >> $GITHUB_ENV
|
|
|
|
- name: Install Python requirements
|
|
run: |
|
|
python -m pip install --upgrade pip setuptools wheel
|
|
pip install -r requirements.d/development.lock.txt
|
|
|
|
- name: Install borgbackup
|
|
run: |
|
|
if [[ "$TOXENV" == *"llfuse"* ]]; then
|
|
pip install -ve ".[llfuse,cockpit,s3,sftp,rclone]"
|
|
elif [[ "$TOXENV" == *"pyfuse3"* ]]; then
|
|
pip install -ve ".[pyfuse3,cockpit,s3,sftp,rclone]"
|
|
elif [[ "$TOXENV" == *"mfusepy"* ]]; then
|
|
pip install -ve ".[mfusepy,cockpit,s3,sftp,rclone]"
|
|
else
|
|
pip install -ve ".[cockpit,s3,sftp,rclone]"
|
|
fi
|
|
|
|
- name: Build Borg fat binaries (${{ matrix.binary }})
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
run: |
|
|
pip install -r requirements.d/pyinstaller.txt
|
|
./scripts/build-borg-using-pyinstaller.sh
|
|
|
|
- name: Smoke-test the built binary (${{ matrix.binary }})
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
run: |
|
|
pushd dist/binary
|
|
echo "single-file binary"
|
|
chmod +x borg.exe
|
|
./borg.exe -V
|
|
echo "single-directory binary"
|
|
chmod +x borg-dir/borg.exe
|
|
./borg-dir/borg.exe -V
|
|
tar czf borg.tgz borg-dir
|
|
popd
|
|
# Ensure locally built binary in ./dist/binary/borg-dir is found during tests
|
|
export PATH="$GITHUB_WORKSPACE/dist/binary/borg-dir:$PATH"
|
|
echo "borg.exe binary in PATH"
|
|
borg.exe -V
|
|
|
|
- name: Prepare binaries (${{ matrix.binary }})
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
env:
|
|
BINARY: ${{ matrix.binary }}
|
|
run: |
|
|
mkdir -p artifacts
|
|
if [ -f dist/binary/borg.exe ]; then
|
|
cp dist/binary/borg.exe "artifacts/$BINARY"
|
|
fi
|
|
if [ -f dist/binary/borg.tgz ]; then
|
|
cp dist/binary/borg.tgz "artifacts/$BINARY.tgz"
|
|
fi
|
|
echo "binary files"
|
|
ls -l artifacts/
|
|
|
|
- name: Attest binaries provenance (${{ matrix.binary }})
|
|
id: attest-binaries
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-path: 'artifacts/*'
|
|
|
|
- name: Name the sigstore bundle after the binaries (${{ matrix.binary }})
|
|
# The attestation is also stored in the GitHub attestations API, but
|
|
# fetching it from there needs a recent gh and a GitHub token (see
|
|
# #10187) - so the sigstore bundle rides along with the binaries and the
|
|
# release job attaches it to the release as <asset>.sigstore.jsonl, just
|
|
# like it does for the sdist, see .github/workflows/release.yml.
|
|
#
|
|
# One attestation covers both the single-file binary and the .tgz, so the
|
|
# same bundle is stored under both names - one for each asset.
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
env:
|
|
BUNDLE_PATH: ${{ steps.attest-binaries.outputs.bundle-path }}
|
|
run: |
|
|
set -euxo pipefail
|
|
# the glob is expanded before the loop body creates any file:
|
|
for asset in artifacts/*; do
|
|
cp "$BUNDLE_PATH" "$asset.sigstore.jsonl"
|
|
done
|
|
ls -l artifacts/
|
|
|
|
- name: Upload binaries (${{ matrix.binary }})
|
|
if: ${{ matrix.binary && startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ matrix.binary }}
|
|
path: artifacts/*
|
|
if-no-files-found: error
|
|
|
|
- name: run tox env
|
|
env:
|
|
# tox passes this through to pytest (pass_env = ["*"])
|
|
PYTEST_ADDOPTS: ${{ !matrix.coverage && '--no-cov' || '' }}
|
|
run: |
|
|
# do not use fakeroot, but run as root. avoids the dreaded EISDIR sporadic failures. see #2482.
|
|
#sudo -E bash -c "tox -e py"
|
|
# Ensure locally built binary in ./dist/binary/borg-dir is found during tests
|
|
export PATH="$GITHUB_WORKSPACE/dist/binary/borg-dir:$PATH"
|
|
tox --skip-missing-interpreters
|
|
|
|
- name: Upload test results to Codecov
|
|
if: ${{ !cancelled() && !contains(matrix.toxenv, 'mypy') && !contains(matrix.toxenv, 'docs') }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ runner.os }}
|
|
python: ${{ matrix.python-version }}
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: test_results
|
|
env_vars: OS,python
|
|
files: test-results.xml
|
|
|
|
- name: Upload coverage to Codecov
|
|
if: ${{ !cancelled() && matrix.coverage }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ runner.os }}
|
|
python: ${{ matrix.python-version }}
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: coverage
|
|
env_vars: OS,python
|
|
files: coverage.xml
|
|
|
|
vm_tests:
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
attestations: write
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 180
|
|
needs: [lint]
|
|
continue-on-error: true
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# pyver: python the test step installs; part of the pip cache key.
|
|
# 3.14+ gets coverage's near-zero-overhead sysmon core, see #9470.
|
|
include:
|
|
- os: freebsd
|
|
version: '15.1'
|
|
display_name: FreeBSD
|
|
pyver: '3.14'
|
|
# Controls binary build and provenance attestation on tags
|
|
do_binaries: true
|
|
artifact_prefix: borg-freebsd-15-x86_64-gh
|
|
|
|
- os: netbsd
|
|
version: '11.0'
|
|
display_name: NetBSD
|
|
pyver: '3.14'
|
|
do_binaries: false
|
|
|
|
- os: openbsd
|
|
version: '7.9'
|
|
display_name: OpenBSD
|
|
pyver: '3.13'
|
|
do_binaries: false
|
|
# extra RAM for the mfs-backed TMPDIR, see the openbsd test step
|
|
memory: 12G
|
|
|
|
- os: omnios
|
|
version: 'r151056'
|
|
display_name: OmniOS
|
|
pyver: '3.13'
|
|
do_binaries: false
|
|
|
|
- os: haiku
|
|
version: 'r1beta6'
|
|
display_name: Haiku
|
|
pyver: '3.14'
|
|
do_binaries: false
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
persist-credentials: false
|
|
|
|
# .pip-cache lives inside the workspace, which cross-platform-actions
|
|
# rsyncs into the VM and back, so wheels built from sdists in one run
|
|
# (most of the VM setup time) are reused by the next one.
|
|
- name: Cache pip-built wheels
|
|
# Not on tags: that is when the release binaries are built and attested,
|
|
# and they should not be able to pick up a poisoned cache entry.
|
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: .pip-cache
|
|
key: ${{ matrix.os }}-${{ matrix.version }}-py${{ matrix.pyver }}-pip-${{ hashFiles('requirements.d/development.lock.txt') }}
|
|
restore-keys: |
|
|
${{ matrix.os }}-${{ matrix.version }}-py${{ matrix.pyver }}-pip-
|
|
|
|
- name: Start VM for ${{ matrix.display_name }}
|
|
id: cross_os
|
|
# a normal boot takes < 1 minute; since 1.4.0 the action bounds
|
|
# waiting for boot itself, this is just an additional safety net.
|
|
timeout-minutes: 15
|
|
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
|
|
with:
|
|
operating_system: ${{ matrix.os }}
|
|
version: ${{ matrix.version }}
|
|
shell: bash
|
|
# the default VM size (2 cpus) leaves half of the runner's 4 vcpus idle
|
|
cpu_count: 4
|
|
memory: ${{ matrix.memory || '8G' }}
|
|
|
|
- name: Test on ${{ matrix.display_name }}
|
|
shell: cpa.sh {0}
|
|
env:
|
|
DO_BINARIES: ${{ matrix.do_binaries }}
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
# a hung test must fail instead of stalling the suite until the
|
|
# job timeout (tox passes all env vars through to pytest).
|
|
export PYTEST_TIMEOUT=300
|
|
|
|
# colored tox/pytest output - the workflow-level env vars do not
|
|
# reach into the VM, so export them here again.
|
|
export PY_COLORS=1
|
|
export TOX_COLORED=yes
|
|
|
|
# see the "Cache pip-built wheels" step
|
|
export PIP_CACHE_DIR="$PWD/.pip-cache"
|
|
|
|
case "${{ matrix.os }}" in
|
|
freebsd)
|
|
export IGNORE_OSVERSION=yes
|
|
sudo -E pkg update -f
|
|
sudo -E pkg install -y liblz4 pkgconf
|
|
sudo -E pkg install -y fusefs-libs
|
|
sudo -E kldload fusefs
|
|
sudo -E sysctl vfs.usermount=1
|
|
sudo -E chmod 666 /dev/fuse
|
|
|
|
# enable POSIX.1e ACLs on the UFS root fs (covers /tmp), so the ACL tests do not skip (#9144)
|
|
sudo mount -u -o acls /
|
|
mount | grep ' / ' | grep -w acls
|
|
sudo -E pkg install -y rust
|
|
sudo -E pkg install -y gmake
|
|
sudo -E pkg install -y git
|
|
sudo -E pkg install -y python314 py314-sqlite3
|
|
sudo ln -sf /usr/local/bin/python3.14 /usr/local/bin/python3
|
|
sudo ln -sf /usr/local/bin/python3.14 /usr/local/bin/python
|
|
sudo ln -sf /usr/local/bin/pip3.14 /usr/local/bin/pip3
|
|
sudo ln -sf /usr/local/bin/pip3.14 /usr/local/bin/pip
|
|
|
|
# required for libsodium/pynacl build
|
|
export MAKE=gmake
|
|
|
|
python -m venv .venv
|
|
. .venv/bin/activate
|
|
python -V
|
|
pip -V
|
|
python -m pip install --upgrade pip wheel
|
|
pip install -r requirements.d/development.lock.txt
|
|
pip install -e ".[mfusepy,cockpit,s3,sftp,rclone]"
|
|
if [[ "${{ matrix.do_binaries }}" == "true" && "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then
|
|
python -m pip install -r requirements.d/pyinstaller.txt
|
|
./scripts/build-borg-using-pyinstaller.sh
|
|
pushd dist/binary
|
|
echo "single-file binary"
|
|
chmod +x borg.exe
|
|
./borg.exe -V
|
|
echo "single-directory binary"
|
|
chmod +x borg-dir/borg.exe
|
|
./borg-dir/borg.exe -V
|
|
tar czf borg.tgz borg-dir
|
|
popd
|
|
mkdir -p artifacts
|
|
if [ -f dist/binary/borg.exe ]; then
|
|
cp -v dist/binary/borg.exe artifacts/${{ matrix.artifact_prefix }}
|
|
fi
|
|
if [ -f dist/binary/borg.tgz ]; then
|
|
cp -v dist/binary/borg.tgz artifacts/${{ matrix.artifact_prefix }}.tgz
|
|
fi
|
|
fi
|
|
export PATH="$(pwd)/dist/binary:$PATH"
|
|
tox -e py314-mfusepy
|
|
;;
|
|
|
|
netbsd)
|
|
arch="$(uname -m)"
|
|
sudo -E mkdir -p /usr/pkg/etc/pkgin
|
|
echo "https://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/${arch}/11.0/All" | sudo tee /usr/pkg/etc/pkgin/repositories.conf > /dev/null
|
|
sudo -E pkgin update
|
|
sudo -E pkgin -y upgrade
|
|
sudo -E pkgin -y install lz4 git
|
|
sudo -E pkgin -y install rust
|
|
sudo -E pkgin -y install pkg-config
|
|
# python 3.14 for coverage's fast sysmon core, see #9470
|
|
sudo -E pkgin -y install py314-pip py314-virtualenv py314-tox
|
|
sudo -E ln -sf /usr/pkg/bin/python3.14 /usr/pkg/bin/python3
|
|
sudo -E ln -sf /usr/pkg/bin/pip3.14 /usr/pkg/bin/pip3
|
|
sudo -E ln -sf /usr/pkg/bin/virtualenv-3.14 /usr/pkg/bin/virtualenv3
|
|
sudo -E ln -sf /usr/pkg/bin/tox-3.14 /usr/pkg/bin/tox3
|
|
|
|
# Ensure base system admin tools are on PATH for the non-root shell
|
|
export PATH="/sbin:/usr/sbin:$PATH"
|
|
|
|
# On the netbsd 11 VM, / is a fs with extended attributes.
|
|
export TMPDIR="/tmp_eafs"
|
|
sudo -E mkdir -p ${TMPDIR}
|
|
sudo -E chmod 1777 ${TMPDIR}
|
|
|
|
touch ${TMPDIR}/testfile
|
|
lsextattr user ${TMPDIR}/testfile && echo "[xattr] *** xattrs SUPPORTED on ${TMPDIR}! ***"
|
|
|
|
tox3 -e py314-none
|
|
;;
|
|
|
|
openbsd)
|
|
# Put the temp tree (pip/cc build temps, pytest tmp dirs and the
|
|
# borg test repos in them) on a memory-backed filesystem instead
|
|
# of the slow FFS disk. Sized generously (the pytest tmp tree is
|
|
# only cleaned up after the run); the VM gets 12G RAM for this.
|
|
sudo mkdir -p /mfs
|
|
# -O2: FFS2 format - mfs defaults to FFS1, whose 32 bit
|
|
# timestamps silently wrap (breaks test_extract_y2261).
|
|
sudo mount_mfs -O2 -s 6g swap /mfs
|
|
sudo chmod 1777 /mfs
|
|
export TMPDIR=/mfs
|
|
|
|
# 7.9 has no python 3.14 pkg yet - move to 3.14 + update pyver when it ships (#9470)
|
|
sudo -E pkg_add lz4 git rust openssl%3.5 py3-pip py3-virtualenv py3-tox
|
|
|
|
export BORG_OPENSSL_NAME=eopenssl35
|
|
tox -e py313-none
|
|
;;
|
|
|
|
omnios)
|
|
# r151056 has no python-314 pkg yet - move to 3.14 + update pyver when it ships (#9470)
|
|
sudo pkg install gcc14 git pkg-config python-313 gnu-make gnu-coreutils rust
|
|
sudo ln -sf /usr/bin/python3.13 /usr/bin/python3
|
|
sudo ln -sf /usr/bin/python3.13-config /usr/bin/python3-config
|
|
sudo python3 -m ensurepip
|
|
sudo python3 -m pip install virtualenv
|
|
|
|
# On omniOS /tmp is swap-backed tmpfs (small, RAM-bound), so the pip/cargo
|
|
# build temps and the pytest temp tree quickly exhaust it ("no space left on
|
|
# device"). /var/tmp is disk-backed (ZFS), so redirect TMPDIR there.
|
|
export TMPDIR=/var/tmp/borg-ci
|
|
mkdir -p "$TMPDIR"
|
|
|
|
# show whether xattrs work on the ZFS-backed TMPDIR (they are files in a
|
|
# hidden per-file attribute directory there, listed via runat(1))
|
|
touch "$TMPDIR/testfile"
|
|
/usr/bin/runat "$TMPDIR/testfile" ls -a && echo "*** xattrs supported on $TMPDIR ***"
|
|
rm "$TMPDIR/testfile"
|
|
|
|
python3 -m venv .venv
|
|
. .venv/bin/activate
|
|
python -V
|
|
pip -V
|
|
python -m pip install --upgrade pip wheel
|
|
pip install -r requirements.d/development.lock.txt
|
|
# no fuse support on omnios in our tests usually
|
|
pip install -e .
|
|
|
|
tox -e py313-none
|
|
;;
|
|
|
|
haiku)
|
|
pkgman refresh
|
|
# already installed:
|
|
# pkgman install -y git pkgconfig lz4 openssl3
|
|
pkgman install -y lz4_devel openssl3_devel
|
|
pkgman install -y rust_bin
|
|
|
|
python3 -m ensurepip --upgrade
|
|
python3 -m pip install --upgrade pip wheel
|
|
python3 -m venv .venv
|
|
. .venv/bin/activate
|
|
|
|
export PKG_CONFIG_PATH="/system/develop/lib/pkgconfig:/system/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
|
|
export BORG_LIBLZ4_PREFIX=/system/develop
|
|
export BORG_OPENSSL_PREFIX=/system/develop
|
|
|
|
# this VM corrupts data every now and then: rustc crashed on crate metadata it
|
|
# had written itself ("assertion failed: bytes[len] == STR_SENTINEL") and pip
|
|
# found sha256 mismatches reading big wheels back from its own cache. Build
|
|
# outside /boot/system/cache/tmp (haiku's /tmp) and run one rustc at a time,
|
|
# in case that corruption comes from disk or memory pressure.
|
|
export TMPDIR=/boot/home/borg-tmp
|
|
mkdir -p "$TMPDIR"
|
|
export CARGO_TARGET_DIR="$TMPDIR/cargo-target"
|
|
export CARGO_BUILD_JOBS=1
|
|
|
|
# retry a pip install that hit a corrupted file, dropping the cached (possibly
|
|
# corrupted) files first, so one of them does not fail the whole job - and does
|
|
# not end up in the actions cache, failing every following run, too.
|
|
pip_install() {
|
|
for attempt in 1 2 3; do
|
|
pip install "$@" && return 0
|
|
echo "*** pip install failed (attempt $attempt), purging the pip cache and retrying ***"
|
|
pip cache purge || true
|
|
done
|
|
return 1
|
|
}
|
|
|
|
pip_install -r requirements.d/development.lock.txt
|
|
pip_install -e .
|
|
|
|
# troubles with either tox or pytest xdist, so we run pytest manually:
|
|
pytest -v -n auto -rs --cov=borg --cov-config=pyproject.toml --cov-report=xml --junitxml=test-results.xml --benchmark-skip -k "not remote and not socket"
|
|
;;
|
|
esac
|
|
|
|
- name: Attest provenance
|
|
id: attest-binaries
|
|
if: startsWith(github.ref, 'refs/tags/') && matrix.do_binaries
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-path: 'artifacts/*'
|
|
|
|
- name: Name the sigstore bundle after the binaries
|
|
# The attestation is also stored in the GitHub attestations API, but
|
|
# fetching it from there needs a recent gh and a GitHub token (see
|
|
# #10187) - so the sigstore bundle rides along with the binaries and the
|
|
# release job attaches it to the release as <asset>.sigstore.jsonl, just
|
|
# like it does for the sdist, see .github/workflows/release.yml.
|
|
#
|
|
# One attestation covers both the single-file binary and the .tgz, so
|
|
# the same bundle is stored under both names - one for each asset.
|
|
# This is also why the attestation is made before the artifact is
|
|
# uploaded and not after it.
|
|
if: startsWith(github.ref, 'refs/tags/') && matrix.do_binaries
|
|
env:
|
|
BUNDLE_PATH: ${{ steps.attest-binaries.outputs.bundle-path }}
|
|
run: |
|
|
set -euxo pipefail
|
|
# the glob is expanded before the loop body creates any file:
|
|
for asset in artifacts/*; do
|
|
cp "$BUNDLE_PATH" "$asset.sigstore.jsonl"
|
|
done
|
|
ls -l artifacts/
|
|
|
|
- name: Upload artifacts
|
|
if: startsWith(github.ref, 'refs/tags/') && matrix.do_binaries
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ matrix.artifact_prefix }}
|
|
path: artifacts/*
|
|
if-no-files-found: ignore
|
|
|
|
- name: Upload test results to Codecov
|
|
if: ${{ !cancelled() }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ matrix.os }}
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: test_results
|
|
env_vars: OS
|
|
files: test-results.xml
|
|
|
|
- name: Upload coverage to Codecov
|
|
if: ${{ !cancelled() }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ matrix.os }}
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: coverage
|
|
env_vars: OS
|
|
files: coverage.xml
|
|
|
|
windows_tests:
|
|
|
|
if: true # can be used to temporarily disable the build
|
|
runs-on: windows-latest
|
|
timeout-minutes: 90
|
|
needs: [lint]
|
|
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
attestations: write
|
|
|
|
env:
|
|
MSYS2_ARG_CONV_EXCL: "*"
|
|
MSYS2_ENV_CONV_EXCL: "*"
|
|
# see the "Cache pip-built wheels" step. MSYS2_ENV_CONV_EXCL above keeps
|
|
# this a Windows path when it enters the msys2 shell.
|
|
PIP_CACHE_DIR: ${{ github.workspace }}\.pip-cache
|
|
|
|
defaults:
|
|
run:
|
|
shell: msys2 {0}
|
|
|
|
steps:
|
|
# actions/checkout runs Git for Windows, whose system config sets
|
|
# core.autocrlf=true, while the msys2 git the build steps below use does
|
|
# not: it would see every text file as modified, so setuptools-scm called
|
|
# the checkout dirty and gave the binaries a guessed-next .devN version
|
|
# instead of the tag version, see #10199. .gitattributes takes care of
|
|
# this as well, this is here so that it also works for older tags.
|
|
- name: Do not convert line endings on checkout
|
|
shell: pwsh
|
|
run: git config --global core.autocrlf false
|
|
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
# MSYS2's mingw Python cannot use PyPI's win_amd64 wheels, so pip builds
|
|
# all compiled deps from source - incl. building maturin via cargo, just
|
|
# to build the blake3 wheel. Persist the wheels pip builds.
|
|
- name: Cache pip-built wheels
|
|
# Not on tags: that is when the release binaries are built and attested,
|
|
# and they should not be able to pick up a poisoned cache entry.
|
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: .pip-cache
|
|
key: windows-msys2-pip-${{ hashFiles('pyproject.toml', 'requirements.d/pyinstaller.txt') }}
|
|
restore-keys: |
|
|
windows-msys2-pip-
|
|
|
|
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
|
|
with:
|
|
msystem: UCRT64
|
|
update: true
|
|
|
|
- name: Install system packages
|
|
run: ./scripts/msys2-install-deps development
|
|
|
|
- name: Build python venv
|
|
run: |
|
|
# building native extensions in the venv fails, so we try to use the system packages
|
|
python -m venv --system-site-packages env
|
|
. env/bin/activate
|
|
# python -m pip install --upgrade pip
|
|
# pip install --upgrade setuptools build wheel
|
|
pip install -r requirements.d/pyinstaller.txt
|
|
|
|
- name: Build
|
|
run: |
|
|
# build borg.exe
|
|
. env/bin/activate
|
|
pip install -e ".[cockpit,s3,sftp,rclone]"
|
|
./scripts/build-borg-using-pyinstaller.sh
|
|
# build sdist and wheel in dist/...
|
|
python -m build
|
|
|
|
# Same layout and naming as the binaries of the other platforms, so that
|
|
# the release job picks this up as a release asset, too. The single-file
|
|
# binary keeps its .exe extension - Windows needs it to run the file.
|
|
- name: Prepare binaries (borg-windows-x86_64-gh)
|
|
run: |
|
|
pushd dist/binary
|
|
echo "single-file binary"
|
|
./borg.exe -V
|
|
echo "single-directory binary"
|
|
./borg-dir/borg.exe -V
|
|
tar czf borg.tgz borg-dir
|
|
popd
|
|
mkdir -p artifacts
|
|
cp dist/binary/borg.exe artifacts/borg-windows-x86_64-gh.exe
|
|
cp dist/binary/borg.tgz artifacts/borg-windows-x86_64-gh.tgz
|
|
echo "binary files"
|
|
ls -l artifacts/
|
|
|
|
- name: Attest binaries provenance (borg-windows-x86_64-gh)
|
|
id: attest-binaries
|
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-path: 'artifacts/*'
|
|
|
|
- name: Name the sigstore bundle after the binaries (borg-windows-x86_64-gh)
|
|
# The attestation is also stored in the GitHub attestations API, but
|
|
# fetching it from there needs a recent gh and a GitHub token (see
|
|
# #10187) - so the sigstore bundle rides along with the binaries and the
|
|
# release job attaches it to the release as <asset>.sigstore.jsonl, just
|
|
# like it does for the sdist, see .github/workflows/release.yml.
|
|
#
|
|
# One attestation covers both the .exe and the .tgz, so the same bundle
|
|
# is stored under both names - one for each asset.
|
|
#
|
|
# pwsh, not the msys2 shell this job otherwise uses: bundle-path is a
|
|
# native Windows path and MSYS2_ARG_CONV_EXCL above keeps msys2 from
|
|
# converting it.
|
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
|
shell: pwsh
|
|
env:
|
|
BUNDLE_PATH: ${{ steps.attest-binaries.outputs.bundle-path }}
|
|
run: |
|
|
Get-ChildItem artifacts -File | ForEach-Object {
|
|
Copy-Item -Path $env:BUNDLE_PATH -Destination "$($_.FullName).sigstore.jsonl"
|
|
}
|
|
Get-ChildItem artifacts
|
|
|
|
- name: Upload binaries (borg-windows-x86_64-gh)
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: borg-windows-x86_64-gh
|
|
path: artifacts/*
|
|
if-no-files-found: error
|
|
|
|
- name: Run tests
|
|
run: |
|
|
# Ensure locally built binary in ./dist/binary/borg-dir is found during tests
|
|
export PATH="$GITHUB_WORKSPACE/dist/binary/borg-dir:$PATH"
|
|
borg.exe -V
|
|
. env/bin/activate
|
|
python -m pytest -n4 --benchmark-skip -vv -rs -k "not remote" --cov=borg --cov-config=pyproject.toml --cov-report=xml --junitxml=test-results.xml
|
|
|
|
- name: Upload test results to Codecov
|
|
if: ${{ !cancelled() }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ runner.os }}
|
|
python: '3.11'
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: test_results
|
|
env_vars: OS,python
|
|
files: test-results.xml
|
|
|
|
- name: Upload coverage to Codecov
|
|
if: ${{ !cancelled() }}
|
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
env:
|
|
OS: ${{ runner.os }}
|
|
python: '3.11'
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
report_type: coverage
|
|
env_vars: OS,python
|
|
files: coverage.xml
|
|
|
|
release:
|
|
# Build the sdist and draft the GitHub release with the binaries the jobs
|
|
# above built for this tag, see .github/workflows/release.yml.
|
|
#
|
|
# vm_tests is continue-on-error (a flaky BSD VM must not stop a release), so
|
|
# this waits for it, but only requires native_tests to have succeeded. A
|
|
# binary that did not get built is warned about while drafting the release.
|
|
if: ${{ !cancelled() && startsWith(github.ref, 'refs/tags/') && needs.native_tests.result == 'success' }}
|
|
needs: [native_tests, vm_tests]
|
|
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
attestations: write
|
|
|
|
uses: ./.github/workflows/release.yml
|
|
|
|
pypi:
|
|
# Upload the sdist the release job built to PyPI.
|
|
#
|
|
# This job can not live in release.yml with the rest of the release code:
|
|
# PyPI trusted publishing does not work from a reusable workflow, see
|
|
# https://docs.pypi.org/trusted-publishers/troubleshooting/
|
|
#
|
|
# One-time setup, so that no API token has to be stored anywhere:
|
|
# - on pypi.org, add a trusted publisher to the "borgbackup" project:
|
|
# owner "borgbackup", repository "borg", workflow "ci.yml",
|
|
# environment "pypi".
|
|
# - create the "pypi" environment in the repository settings. Configuring
|
|
# required reviewers for it makes this (irreversible) upload wait for an
|
|
# approval, which is the last chance to stop a release.
|
|
if: ${{ startsWith(github.ref, 'refs/tags/') && needs.release.result == 'success' }}
|
|
needs: [release]
|
|
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 30
|
|
|
|
environment:
|
|
name: pypi
|
|
url: https://pypi.org/project/borgbackup/
|
|
|
|
permissions:
|
|
contents: read
|
|
id-token: write # trusted publishing
|
|
|
|
steps:
|
|
- name: Get the sdist built by the release job
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: sdist
|
|
path: dist
|
|
|
|
- name: What we are about to upload
|
|
run: ls -l dist/
|
|
|
|
- name: Upload to PyPI
|
|
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|