ci.yml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. # badge: https://github.com/borgbackup/borg/workflows/CI/badge.svg?branch=master
  2. name: CI
  3. on:
  4. push:
  5. branches: [ master ]
  6. paths:
  7. - '**.py'
  8. - '**.pyx'
  9. - '**.c'
  10. - '**.h'
  11. - '**.yml'
  12. - '**.toml'
  13. - '**.cfg'
  14. - '**.ini'
  15. - 'requirements.d/*'
  16. - '!docs/**'
  17. pull_request:
  18. branches: [ master ]
  19. paths:
  20. - '**.py'
  21. - '**.pyx'
  22. - '**.c'
  23. - '**.h'
  24. - '**.yml'
  25. - '**.toml'
  26. - '**.cfg'
  27. - '**.ini'
  28. - 'requirements.d/*'
  29. - '!docs/**'
  30. jobs:
  31. lint:
  32. runs-on: ubuntu-22.04
  33. timeout-minutes: 5
  34. steps:
  35. - uses: actions/checkout@v4
  36. - uses: chartboost/ruff-action@v1
  37. security:
  38. runs-on: ubuntu-24.04
  39. timeout-minutes: 5
  40. steps:
  41. - uses: actions/checkout@v4
  42. - name: Set up Python
  43. uses: actions/setup-python@v5
  44. with:
  45. python-version: '3.10'
  46. - name: Install dependencies
  47. run: |
  48. python -m pip install --upgrade pip
  49. pip install bandit[toml]
  50. - name: Run Bandit
  51. run: |
  52. bandit -r src/borg -c pyproject.toml
  53. posix_tests:
  54. needs: [lint, security]
  55. strategy:
  56. fail-fast: true
  57. # noinspection YAMLSchemaValidation
  58. matrix: >-
  59. ${{ fromJSON(
  60. github.event_name == 'pull_request' && '{
  61. "include": [
  62. {"os": "ubuntu-22.04", "python-version": "3.10", "toxenv": "mypy"},
  63. {"os": "ubuntu-22.04", "python-version": "3.11", "toxenv": "docs"},
  64. {"os": "ubuntu-22.04", "python-version": "3.10", "toxenv": "py310-fuse2"},
  65. {"os": "ubuntu-24.04", "python-version": "3.14", "toxenv": "py314-fuse3"}
  66. ]
  67. }' || '{
  68. "include": [
  69. {"os": "ubuntu-22.04", "python-version": "3.10", "toxenv": "mypy"},
  70. {"os": "ubuntu-22.04", "python-version": "3.11", "toxenv": "docs"},
  71. {"os": "ubuntu-22.04", "python-version": "3.10", "toxenv": "py310-fuse2"},
  72. {"os": "ubuntu-22.04", "python-version": "3.11", "toxenv": "py311-fuse2", "binary": "borg-linux-glibc235-x86_64-gh"},
  73. {"os": "ubuntu-22.04-arm", "python-version": "3.11", "toxenv": "py311-fuse2", "binary": "borg-linux-glibc235-arm64-gh"},
  74. {"os": "ubuntu-24.04", "python-version": "3.12", "toxenv": "py312-fuse3"},
  75. {"os": "ubuntu-24.04", "python-version": "3.13", "toxenv": "py313-fuse3"},
  76. {"os": "ubuntu-24.04", "python-version": "3.14", "toxenv": "py314-fuse3"},
  77. {"os": "macos-13", "python-version": "3.11", "toxenv": "py311-none", "binary": "borg-macos-13-x86_64-gh"},
  78. {"os": "macos-14", "python-version": "3.11", "toxenv": "py311-none", "binary": "borg-macos-14-arm64-gh"}
  79. ]
  80. }'
  81. ) }}
  82. env:
  83. TOXENV: ${{ matrix.toxenv }}
  84. runs-on: ${{ matrix.os }}
  85. timeout-minutes: 120
  86. steps:
  87. - uses: actions/checkout@v4
  88. with:
  89. # Just fetching one commit is not enough for setuptools-scm, so we fetch all.
  90. fetch-depth: 0
  91. fetch-tags: true
  92. - name: Detect if commit is tagged
  93. id: detect_tag
  94. run: |
  95. tag="$(git describe --exact-match --tags HEAD 2>/dev/null || true)"
  96. # If HEAD is a merge commit, the PR head is usually the second parent (HEAD^2).
  97. if [ -z "$tag" ] && git rev-parse -q --verify HEAD^2 >/dev/null 2>&1; then
  98. tag="$(git describe --exact-match --tags HEAD^2 2>/dev/null || true)"
  99. fi
  100. echo "Found tag: ${tag}"
  101. echo "tagged=$tag" >> "$GITHUB_OUTPUT"
  102. - name: Check out exact tag
  103. if: ${{ steps.detect_tag.outputs.tagged }}
  104. uses: actions/checkout@v4
  105. with:
  106. ref: ${{ steps.detect_tag.outputs.tagged }}
  107. fetch-depth: 0
  108. fetch-tags: true
  109. - name: Set up Python ${{ matrix.python-version }}
  110. uses: actions/setup-python@v5
  111. with:
  112. python-version: ${{ matrix.python-version }}
  113. - name: Cache pip
  114. uses: actions/cache@v4
  115. with:
  116. path: ~/.cache/pip
  117. key: ${{ runner.os }}-pip-${{ hashFiles('requirements.d/development.txt') }}
  118. restore-keys: |
  119. ${{ runner.os }}-pip-
  120. ${{ runner.os }}-
  121. - name: Install Linux packages
  122. if: ${{ runner.os == 'Linux' }}
  123. run: |
  124. sudo apt-get update
  125. sudo apt-get install -y pkg-config build-essential
  126. sudo apt-get install -y libssl-dev libacl1-dev libxxhash-dev liblz4-dev libzstd-dev
  127. sudo apt-get install -y libfuse-dev fuse || true # Required for Python llfuse module
  128. sudo apt-get install -y libfuse3-dev fuse3 || true # Required for Python pyfuse3 module
  129. sudo apt-get install -y bash zsh fish # for shell completion tests
  130. sudo apt-get install -y rclone openssh-server curl
  131. - name: Install macOS packages
  132. if: ${{ runner.os == 'macOS' }}
  133. run: |
  134. brew unlink pkg-config@0.29.2 || true
  135. brew bundle install
  136. - name: Configure OpenSSH SFTP server (test only)
  137. if: ${{ runner.os == 'Linux' }}
  138. run: |
  139. sudo mkdir -p /run/sshd
  140. sudo useradd -m -s /bin/bash sftpuser || true
  141. # Create SSH key for the CI user and authorize it for sftpuser
  142. mkdir -p ~/.ssh
  143. chmod 700 ~/.ssh
  144. test -f ~/.ssh/id_ed25519 || ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
  145. sudo mkdir -p /home/sftpuser/.ssh
  146. sudo chmod 700 /home/sftpuser/.ssh
  147. sudo cp ~/.ssh/id_ed25519.pub /home/sftpuser/.ssh/authorized_keys
  148. sudo chown -R sftpuser:sftpuser /home/sftpuser/.ssh
  149. sudo chmod 600 /home/sftpuser/.ssh/authorized_keys
  150. # Allow publickey auth and enable Subsystem sftp
  151. sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
  152. sudo sed -i 's/^#\?PubkeyAuthentication .*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
  153. 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
  154. # Ensure host keys exist to avoid slow generation on first sshd start
  155. sudo ssh-keygen -A
  156. # Start sshd (listen on default 22 inside runner)
  157. sudo /usr/sbin/sshd -D &
  158. # Add host key to known_hosts so paramiko trusts it
  159. ssh-keyscan -H localhost 127.0.0.1 | tee -a ~/.ssh/known_hosts
  160. # Start ssh-agent and add our key so paramiko can use the agent
  161. eval "$(ssh-agent -s)"
  162. ssh-add ~/.ssh/id_ed25519
  163. # Export SFTP test URL for tox via GITHUB_ENV
  164. echo "BORG_TEST_SFTP_REPO=sftp://sftpuser@localhost:22/borg/sftp-repo" >> $GITHUB_ENV
  165. - name: Install and configure MinIO S3 server (test only)
  166. if: ${{ runner.os == 'Linux' }}
  167. run: |
  168. set -e
  169. arch=$(uname -m)
  170. case "$arch" in
  171. 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 ;;
  172. 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 ;;
  173. *) echo "Unsupported arch: $arch"; exit 1 ;;
  174. esac
  175. curl -fsSL -o /usr/local/bin/minio "$srv_url"
  176. curl -fsSL -o /usr/local/bin/mc "$cli_url"
  177. sudo chmod +x /usr/local/bin/minio /usr/local/bin/mc
  178. export PATH=/usr/local/bin:$PATH
  179. # Start MinIO on :9000 with default credentials (minioadmin/minioadmin)
  180. MINIO_DIR="$GITHUB_WORKSPACE/.minio-data"
  181. MINIO_LOG="$GITHUB_WORKSPACE/.minio.log"
  182. mkdir -p "$MINIO_DIR"
  183. nohup minio server "$MINIO_DIR" --address ":9000" >"$MINIO_LOG" 2>&1 &
  184. # Wait for MinIO port to be ready
  185. for i in $(seq 1 60); do (echo > /dev/tcp/127.0.0.1/9000) >/dev/null 2>&1 && break; sleep 1; done
  186. # Configure client and create bucket
  187. mc alias set local http://127.0.0.1:9000 minioadmin minioadmin
  188. mc mb --ignore-existing local/borg
  189. # Export S3 test URL for tox via GITHUB_ENV
  190. echo "BORG_TEST_S3_REPO=s3:minioadmin:minioadmin@http://127.0.0.1:9000/borg/s3-repo" >> $GITHUB_ENV
  191. - name: Install Python requirements
  192. run: |
  193. python -m pip install --upgrade pip setuptools wheel
  194. pip install -r requirements.d/development.txt
  195. - name: Install borgbackup
  196. run: |
  197. pip install -e .
  198. - name: run tox env
  199. env:
  200. XDISTN: "4"
  201. run: |
  202. # do not use fakeroot, but run as root. avoids the dreaded EISDIR sporadic failures. see #2482.
  203. #sudo -E bash -c "tox -e py"
  204. tox --skip-missing-interpreters
  205. - name: Upload coverage to Codecov
  206. uses: codecov/codecov-action@v4
  207. env:
  208. OS: ${{ runner.os }}
  209. python: ${{ matrix.python-version }}
  210. with:
  211. token: ${{ secrets.CODECOV_TOKEN }}
  212. env_vars: OS, python
  213. - name: Build Borg fat binaries (${{ matrix.binary }})
  214. if: ${{ matrix.binary && steps.detect_tag.outputs.tagged }}
  215. run: |
  216. pip install 'pyinstaller==6.14.2'
  217. mkdir -p dist/binary
  218. pyinstaller --clean --distpath=dist/binary scripts/borg.exe.spec
  219. - name: Smoke-test the built binary (${{ matrix.binary }})
  220. if: ${{ matrix.binary && steps.detect_tag.outputs.tagged }}
  221. run: |
  222. pushd dist/binary
  223. echo "single-file binary"
  224. chmod +x borg.exe
  225. ./borg.exe -V
  226. echo "single-directory binary"
  227. chmod +x borg-dir/borg.exe
  228. ./borg-dir/borg.exe -V
  229. tar czf borg.tgz borg-dir
  230. popd
  231. - name: Prepare binaries (${{ matrix.binary }})
  232. if: ${{ matrix.binary && steps.detect_tag.outputs.tagged }}
  233. run: |
  234. mkdir -p artifacts
  235. if [ -f dist/binary/borg.exe ]; then
  236. cp dist/binary/borg.exe artifacts/${{ matrix.binary }}
  237. fi
  238. if [ -f dist/binary/borg.tgz ]; then
  239. cp dist/binary/borg.tgz artifacts/${{ matrix.binary }}.tgz
  240. fi
  241. echo "binary files"
  242. ls -l artifacts/
  243. - name: Upload binaries (${{ matrix.binary }})
  244. if: ${{ matrix.binary && steps.detect_tag.outputs.tagged }}
  245. uses: actions/upload-artifact@v4
  246. with:
  247. name: ${{ matrix.binary }}
  248. path: artifacts/*
  249. if-no-files-found: error
  250. windows_tests:
  251. if: false # can be used to temporarily disable the build
  252. runs-on: windows-latest
  253. timeout-minutes: 120
  254. needs: posix_tests
  255. env:
  256. PY_COLORS: 1
  257. defaults:
  258. run:
  259. shell: msys2 {0}
  260. steps:
  261. - uses: actions/checkout@v4
  262. with:
  263. fetch-depth: 0
  264. - uses: msys2/setup-msys2@v2
  265. with:
  266. msystem: UCRT64
  267. update: true
  268. - name: Install system packages
  269. run: ./scripts/msys2-install-deps development
  270. - name: Build python venv
  271. run: |
  272. # building cffi / argon2-cffi in the venv fails, so we try to use the system packages
  273. python -m venv --system-site-packages env
  274. . env/bin/activate
  275. # python -m pip install --upgrade pip
  276. # pip install --upgrade setuptools build wheel
  277. pip install pyinstaller==6.14.2
  278. - name: Build
  279. run: |
  280. # build borg.exe
  281. . env/bin/activate
  282. pip install -e .
  283. pyinstaller -y scripts/borg.exe.spec
  284. # build sdist and wheel in dist/...
  285. python -m build
  286. - uses: actions/upload-artifact@v4
  287. with:
  288. name: borg-windows
  289. path: dist/borg.exe
  290. - name: Run tests
  291. run: |
  292. ./dist/borg.exe -V
  293. . env/bin/activate
  294. borg -V
  295. python -m pytest -n4 --benchmark-skip -vv -rs -k "not remote"
  296. - name: Upload coverage to Codecov
  297. uses: codecov/codecov-action@v4
  298. env:
  299. OS: ${{ runner.os }}
  300. python: '3.11'
  301. with:
  302. token: ${{ secrets.CODECOV_TOKEN }}
  303. env_vars: OS, python