diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 976d57365e11..000000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,439 +0,0 @@ -# unittests using the 'latest' runtime python-dependencies -task: - container: - image: $ELECTRUM_IMAGE - cpu: 1 - memory: 2G - matrix: - - name: "unittests: py$ELECTRUM_PYTHON_VERSION" - env: - ELECTRUM_IMAGE: python:$ELECTRUM_PYTHON_VERSION - matrix: - - env: - ELECTRUM_PYTHON_VERSION: 3.10 - - env: - ELECTRUM_PYTHON_VERSION: 3.11 - - env: - ELECTRUM_PYTHON_VERSION: 3.12 - - env: - ELECTRUM_PYTHON_VERSION: 3.13 - - env: - ELECTRUM_PYTHON_VERSION: 3.14 - - name: "unittests: py3.14, debug-mode" - env: - ELECTRUM_PYTHON_VERSION: 3.14 - # enable additional checks: - PYTHONASYNCIODEBUG: "1" - PYTHONDEVMODE: "1" - pip_cache: - folder: ~/.cache/pip - fingerprint_script: echo $ELECTRUM_IMAGE && cat $ELECTRUM_REQUIREMENTS_CI && cat $ELECTRUM_REQUIREMENTS - tag_script: - - git tag - libsecp_build_cache: - folder: contrib/_saved_secp256k1_build - fingerprint_script: sha256sum ./contrib/make_libsecp256k1.sh - populate_script: - - apt-get update - - apt-get -y install automake libtool - - ./contrib/make_libsecp256k1.sh - - mkdir contrib/_saved_secp256k1_build - - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ - install_script: - - apt-get update - # qml test reqs: - - apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3 - - pip install -r $ELECTRUM_REQUIREMENTS_CI - # electrum itself: - - export ELECTRUM_ECC_DONT_COMPILE=1 - - pip install ".[tests,qml_gui]" - version_script: - - python3 --version - - pip freeze --all - pytest_script: - - > - coverage run --source=electrum \ - "--omit=electrum/gui/*,electrum/plugins/*,electrum/scripts/*" \ - -m pytest tests -v - - coverage report - coveralls_script: - - if [ ! -z "$COVERALLS_REPO_TOKEN" ] && [ "$ELECTRUM_PYTHON_VERSION" = "3.10" ] ; then coveralls ; fi - env: - LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ - ELECTRUM_REQUIREMENTS_CI: contrib/requirements/requirements-ci.txt - ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt - # following CI_* env vars are set up for coveralls - CI_NAME: "CirrusCI" - CI_BUILD_NUMBER: $CIRRUS_BUILD_ID - CI_JOB_ID: $CIRRUS_TASK_ID - CI_BUILD_URL: "https://cirrus-ci.com/task/$CIRRUS_TASK_ID" - CI_BRANCH: $CIRRUS_BRANCH - CI_PULL_REQUEST: $CIRRUS_PR - # in addition, COVERALLS_REPO_TOKEN is set as an "override" in https://cirrus-ci.com/settings/... - depends_on: - - "linter: Flake8 Mandatory" - -# unittests using the ~same frozen dependencies that are used in the released binaries -# note: not using pinned pyqt here, due to "qml_gui" extra -task: - container: - image: $ELECTRUM_IMAGE - cpu: 1 - memory: 2G - name: "unittests: py3.10, frozen-deps" - pip_cache: - folder: ~/.cache/pip - fingerprint_script: echo $ELECTRUM_IMAGE && cat contrib/requirements/requirements*.txt && cat contrib/deterministic-build/requirements*.txt - tag_script: - - git tag - libsecp_build_cache: - folder: contrib/_saved_secp256k1_build - fingerprint_script: sha256sum ./contrib/make_libsecp256k1.sh - populate_script: - - apt-get update - - apt-get -y install automake libtool - - ./contrib/make_libsecp256k1.sh - - mkdir contrib/_saved_secp256k1_build - - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ - install_script: - - apt-get update - # qml test reqs: - - apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3 - - pip install -r contrib/deterministic-build/requirements-build-base.txt - - pip install -r contrib/requirements/requirements-ci.txt - # electrum itself: - - export ELECTRUM_ECC_DONT_COMPILE=1 - - pip install -r contrib/deterministic-build/requirements.txt -r contrib/deterministic-build/requirements-binaries.txt - - pip install ".[tests,qml_gui]" - version_script: - - python3 --version - - pip freeze --all - pytest_script: - - pytest tests -v - env: - ELECTRUM_IMAGE: python:3.10 - LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ - depends_on: - - "linter: Flake8 Mandatory" - -task: - name: "locale: upload to crowdin" - container: - image: $ELECTRUM_IMAGE - cpu: 1 - memory: 1G - pip_cache: - folder: ~/.cache/pip - fingerprint_script: echo Locale && echo $ELECTRUM_IMAGE && cat $ELECTRUM_REQUIREMENTS_CI - install_script: - - apt-get update - - apt-get -y install gettext qt6-l10n-tools - - pip install -r $ELECTRUM_REQUIREMENTS_CI - - pip install requests - submodules_script: - - git submodule update --init - locale_script: - - contrib/locale/push_locale.py - env: - ELECTRUM_IMAGE: python:3.10 - ELECTRUM_REQUIREMENTS_CI: contrib/requirements/requirements-ci.txt - # in addition, crowdin_api_key is set as an "override" in https://cirrus-ci.com/settings/... - # - api key is for crowdin account: "SomberNight_CI_BOT" - # - see https://crowdin.com/settings#api-key - depends_on: - - "unittests: py3.10" - only_if: $CIRRUS_BRANCH == 'master' - -task: - name: "Regtest functional tests" - compute_engine_instance: - image_project: cirrus-images - image: family/docker-builder - platform: linux - cpu: 1 - memory: 1G - pip_cache: - folder: ~/.cache/pip - fingerprint_script: echo Regtest && echo docker_builder && cat $ELECTRUM_REQUIREMENTS - bitcoind_cache: - folder: /tmp/bitcoind - populate_script: mkdir -p /tmp/bitcoind - install_script: - - apt-get update - - apt-get -y install curl jq bc - - python3 -m pip install --user --upgrade pip - # install electrum - - export ELECTRUM_ECC_DONT_COMPILE=1 # we build manually to make caching it easier - - python3 -m pip install .[tests] --ignore-installed # ignore installed system installed attrs - # install e-x some commits after 1.18.0 tag - - python3 -m pip install git+https://github.com/spesmilo/electrumx.git@0b260d4345242cc41e316e97d7de10ae472fd172 - - "BITCOIND_VERSION=$(curl https://bitcoincore.org/en/download/ | grep -E -i --only-matching 'Latest version: [0-9\\.]+' | grep -E --only-matching '[0-9\\.]+')" - - BITCOIND_FILENAME=bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz - - BITCOIND_PATH=/tmp/bitcoind/$BITCOIND_FILENAME - - BITCOIND_URL=https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/$BITCOIND_FILENAME - - tar -xaf $BITCOIND_PATH || (rm -f /tmp/bitcoind/* && curl --output $BITCOIND_PATH $BITCOIND_URL && tar -xaf $BITCOIND_PATH) - - cp -a bitcoin-$BITCOIND_VERSION/* /usr/ - libsecp_build_cache: - folder: contrib/_saved_secp256k1_build - fingerprint_script: sha256sum ./contrib/make_libsecp256k1.sh - populate_script: - - apt-get -y install automake libtool - - ./contrib/make_libsecp256k1.sh - - mkdir contrib/_saved_secp256k1_build - - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ - bitcoind_service_background_script: - - tests/regtest/run_bitcoind.sh - electrumx_service_background_script: - - tests/regtest/run_electrumx.sh - # if any test fails, the test will get aborted (--failfast) and the wallet directories will be - # available for download in the Cirrus UI - regtest_script: - - sleep 10s - - python3 -m unittest tests/regtest.py --failfast || TEST_EXIT_CODE=$? - - tar -czf test_wallets.tar.gz /tmp/alice /tmp/bob /tmp/carol || true - - exit ${TEST_EXIT_CODE:-0} - on_failure: - wallet_artifacts: - path: "test_wallets.tar.gz" - env: - LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ - ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt - PIP_BREAK_SYSTEM_PACKAGES: 1 - # ElectrumX exits with an error without this: - ALLOW_ROOT: 1 - depends_on: - - "linter: Flake8 Mandatory" - -task: - container: - image: $ELECTRUM_IMAGE - cpu: 1 - memory: 1G - pip_cache: - folder: ~/.cache/pip - fingerprint_script: echo Flake8 && echo $ELECTRUM_IMAGE && cat $ELECTRUM_REQUIREMENTS - install_script: - - pip install "flake8==7.3.0" "flake8-bugbear==25.10.21" - flake8_script: - - flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "*_pb2.py,electrum/_vendor/" - env: - ELECTRUM_IMAGE: python:3.10 - ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt - matrix: - - name: "linter: Flake8 Mandatory" - env: - # list of error codes: - # - https://flake8.pycqa.org/en/latest/user/error-codes.html - # - https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes - # - https://github.com/PyCQA/flake8-bugbear/tree/8c0e7eb04217494d48d0ab093bf5b31db0921989#list-of-warnings - ELECTRUM_LINTERS: E9,E101,E129,E273,E274,E703,E71,E722,F5,F6,F7,F8,W191,W29,B,B909 - ELECTRUM_LINTERS_IGNORE: B007,B009,B010,B036,B042,F541,F841 - - name: "linter: Flake8 Non-Mandatory" - env: - ELECTRUM_LINTERS: E,F,W,C90,B - ELECTRUM_LINTERS_IGNORE: "" - allow_failures: true - -task: - name: "linter: ban unicode" - container: - image: python:3.10 - cpu: 1 - memory: 1G - main_script: - - contrib/ban_unicode.py - -task: - name: "security review: Claude Code" - # NOTE: claude has access to all API keys available in the Cirrus CI environment. - # If we would add some critical api keys in here we should consider this. - matrix: - - trigger_type: automatic - only_if: $CIRRUS_PR != '' && ($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin') - - trigger_type: manual - only_if: $CIRRUS_PR != '' && !($CIRRUS_USER_PERMISSION == 'write' || $CIRRUS_USER_PERMISSION == 'admin') - container: - image: node:20 - cpu: 1 - memory: 2G - # CLAUDE_CODE_OAUTH_TOKEN is set as an encrypted "override" in https://cirrus-ci.com/settings/... - # It must be stored encrypted (ENCRYPTED[...]) so Cirrus CI refuses to decrypt it for - # fork PRs from users without write permission. - # Generate with: claude setup-token - # Optional: set GITHUB_TOKEN to enable PR comments on failure - install_script: - - npm install -g @anthropic-ai/claude-code - review_script: - - python3 contrib/ci/claude_security_review.py - -# Cron jobs configured in https://cirrus-ci.com/settings/... -# - job "nightly" on branch "master" at "0 30 2 * * ?" (every day at 02:30Z) -task: - name: "build: Windows" - matrix: - - trigger_type: manual - only_if: $CIRRUS_CRON == "" - - trigger_type: automatic - only_if: $CIRRUS_CRON == "nightly" - container: - dockerfile: contrib/build-wine/Dockerfile - cpu: 1 - memory: 3G - pip_cache: - folders: - - contrib/build-wine/.cache/win*/wine_pip_cache - fingerprint_script: - - echo $CIRRUS_TASK_NAME - - git ls-files -s contrib/deterministic-build/*.txt - - git ls-files -s contrib/build-wine/ - build2_cache: - folders: - - contrib/build-wine/.cache/win*/build - fingerprint_script: - - echo $CIRRUS_TASK_NAME - - cat contrib/make_libsecp256k1.sh | sha256sum - - cat contrib/make_libusb.sh | sha256sum - - cat contrib/make_zbar.sh | sha256sum - - git ls-files -s contrib/build-wine/ - build_script: - - cd contrib/build-wine - - ./make_win.sh - binaries_artifacts: - path: "contrib/build-wine/dist/*" - env: - CIRRUS_WORKING_DIR: /opt/wine64/drive_c/electrum - CIRRUS_DOCKER_CONTEXT: contrib/build-wine - depends_on: - - "unittests: py3.10" - -task: - name: "build: Android (QML $APK_ARCH)" - matrix: - - trigger_type: manual - only_if: $CIRRUS_CRON == "" - - trigger_type: automatic - only_if: $CIRRUS_CRON == "nightly" - timeout_in: 90m - container: - dockerfile: contrib/android/Dockerfile - cpu: 8 - memory: 24G - env: - APK_ARCH: arm64-v8a - packages_tld_folder_cache: - folder: packages - fingerprint_script: - - echo $CIRRUS_TASK_NAME && cat contrib/deterministic-build/requirements.txt && cat contrib/make_packages.sh - - git ls-files -s contrib/android/ - p4a_cache: - folders: - - ".buildozer/android/platform/build-$APK_ARCH/packages" - - ".buildozer/android/platform/build-$APK_ARCH/build" - fingerprint_script: - # note: should *at least* depend on Dockerfile and p4a_recipes/, but contrib/android/ is simplest - - git ls-files -s contrib/android/ - - echo "qml $APK_ARCH" - build_script: - - ./contrib/android/make_apk.sh qml "$APK_ARCH" debug - binaries_artifacts: - path: "dist/*" - depends_on: - - "unittests: py3.10" - -## mac build disabled, as Cirrus CI no longer supports Intel-based mac builds -#task: -# name: "build: macOS" -# macos_instance: -# image: catalina-xcode-11.3.1 -# env: -# TARGET_OS: macOS -# pip_cache: -# folder: ~/Library/Caches/pip -# fingerprint_script: -# - echo $CIRRUS_TASK_NAME -# - git ls-files -s contrib/deterministic-build/*.txt -# - git ls-files -s contrib/osx/ -# build2_cache: -# folder: contrib/osx/.cache -# fingerprint_script: -# - echo $CIRRUS_TASK_NAME -# - cat contrib/make_libsecp256k1.sh | shasum -a 256 -# - cat contrib/make_libusb.sh | shasum -a 256 -# - cat contrib/make_zbar.sh | shasum -a 256 -# - git ls-files -s contrib/osx/ -# install_script: -# - git fetch --all --tags -# build_script: -# - ./contrib/osx/make_osx.sh -# sum_script: -# - ls -lah dist -# - shasum -a 256 dist/*.dmg -# binaries_artifacts: -# path: "dist/*" - -task: - name: "build: AppImage" - matrix: - - trigger_type: manual - only_if: $CIRRUS_CRON == "" - - trigger_type: automatic - only_if: $CIRRUS_CRON == "nightly" - compute_engine_instance: - image_project: cirrus-images - image: family/docker-builder - platform: linux - cpu: 2 - memory: 2G - pip_cache: - folder: contrib/build-linux/appimage/.cache/pip_cache - fingerprint_script: - - echo $CIRRUS_TASK_NAME - - git ls-files -s contrib/deterministic-build/*.txt - - git ls-files -s contrib/build-linux/appimage/ - build2_cache: - folder: contrib/build-linux/appimage/.cache/appimage - fingerprint_script: - - echo $CIRRUS_TASK_NAME - - cat contrib/make_libsecp256k1.sh | sha256sum - - git ls-files -s contrib/build-linux/appimage/ - build_script: - - ./contrib/build-linux/appimage/build.sh - binaries_artifacts: - path: "dist/*" - depends_on: - - "unittests: py3.10" - -task: - container: - dockerfile: contrib/build-linux/sdist/Dockerfile - cpu: 1 - memory: 1G - pip_cache: - folder: ~/.cache/pip - fingerprint_script: - - echo $CIRRUS_TASK_NAME - - git ls-files -s contrib/deterministic-build/*.txt - - git ls-files -s contrib/build-linux/sdist/ - build_script: - - ./contrib/build-linux/sdist/make_sdist.sh - binaries_artifacts: - path: "dist/*" - matrix: - - name: "build: tarball" - - name: "build: source-only tarball" - env: - OMIT_UNCLEAN_FILES: 1 - depends_on: - - "unittests: py3.10" - -task: - name: "check submodules" - container: - image: python:3.10 - cpu: 1 - memory: 1G - fetch_script: - - git fetch --all --tags - check_script: - - ./contrib/deterministic-build/check_submodules.sh - only_if: $CIRRUS_TAG != '' diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml new file mode 100644 index 000000000000..e20a9a02d632 --- /dev/null +++ b/.github/workflows/builds.yml @@ -0,0 +1,204 @@ +name: builds + +on: + schedule: + - cron: '30 2 * * *' # 02:30 UTC daily + workflow_dispatch: + inputs: + target: + description: 'Which build(s) to run' + required: true + default: 'all' + type: choice + options: [all, windows, android, appimage, tarball] + android_arch: + description: 'Android architecture (when running android)' + required: false + default: 'arm64-v8a' + type: choice + options: [arm64-v8a, armeabi-v7a, x86_64] + +permissions: + contents: read + +concurrency: + group: builds-${{ github.ref }} + cancel-in-progress: false # never kill a nightly or in-progress build + +env: + ARTIFACT_RETENTION: '14' + +jobs: + windows: + name: "build: Windows" + if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'windows' }} + runs-on: ubuntu-24.04 + timeout-minutes: 90 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # for git describe / version detection + + - name: Cache Wine pip + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/build-wine/.cache/win*/wine_pip_cache + key: wine-pip-${{ hashFiles('contrib/deterministic-build/*.txt', 'contrib/build-wine/**') }} + + - name: Cache Wine native lib builds (libsecp256k1, libusb, libzbar) + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/build-wine/.cache/win*/build + key: wine-build-${{ hashFiles('contrib/make_libsecp256k1.sh', 'contrib/make_libusb.sh', 'contrib/make_zbar.sh', 'contrib/build-wine/**') }} + + - name: Build Windows binaries + run: ./contrib/build-wine/build.sh + + - name: List output + run: ls -lah contrib/build-wine/dist/ + + - name: Upload Windows artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: electrum-windows-${{ github.sha }} + path: contrib/build-wine/dist/* + retention-days: ${{ env.ARTIFACT_RETENTION }} + if-no-files-found: error + compression-level: 0 + + android: + name: "build: Android (${{ inputs.android_arch || 'arm64-v8a' }})" + if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'android' }} + runs-on: ubuntu-24.04 + timeout-minutes: 180 + env: + APK_ARCH: ${{ inputs.android_arch || 'arm64-v8a' }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + # GitHub guarantees 14 GB of free space, but we can delete some things for more space. + - name: Free disk space + run: | + df -h + sudo rm -rf \ + /usr/share/dotnet \ + /usr/share/swift \ + /usr/local/lib/android \ + /usr/local/.ghcup \ + /usr/local/share/powershell \ + /opt/ghc \ + /opt/hostedtoolcache/CodeQL \ + /opt/microsoft \ + /opt/pipx + sudo docker image prune --all --force || true + df -h + + - name: Cache python packages + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: packages + key: android-packages-${{ env.APK_ARCH }}-${{ hashFiles('contrib/deterministic-build/requirements.txt', 'contrib/make_packages.sh', 'contrib/android/**') }} + + - name: Cache buildozer (p4a) + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + .buildozer_qml/android/platform/build-${{ env.APK_ARCH }}/packages + .buildozer_qml/android/platform/build-${{ env.APK_ARCH }}/build + # note: should *at least* depend on Dockerfile and p4a_recipes/, but contrib/android/ is simplest + key: android-buildozer-${{ env.APK_ARCH }}-${{ hashFiles('contrib/android/**') }} + + - name: Build APK (debug) + run: ./contrib/android/build.sh qml "$APK_ARCH" debug + + - name: List output + run: ls -lah dist/ + + - name: Upload Android APK + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: electrum-android-${{ env.APK_ARCH }}-${{ github.sha }} + path: dist/* + retention-days: ${{ env.ARTIFACT_RETENTION }} + if-no-files-found: error + compression-level: 0 + + appimage: + name: "build: AppImage" + if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'appimage' }} + runs-on: ubuntu-24.04 + timeout-minutes: 90 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Cache AppImage pip + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/build-linux/appimage/.cache/pip_cache + key: appimage-pip-${{ hashFiles('contrib/deterministic-build/*.txt', 'contrib/build-linux/appimage/**') }} + + - name: Cache AppImage build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/build-linux/appimage/.cache/appimage + key: appimage-build-${{ hashFiles('contrib/make_libsecp256k1.sh', 'contrib/make_zbar.sh', 'contrib/build-linux/appimage/**') }} + + - name: Build AppImage + run: ./contrib/build-linux/appimage/build.sh + + - name: List output + run: ls -lah dist/ + + - name: Upload AppImage + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: electrum-appimage-${{ github.sha }} + path: dist/*.AppImage + retention-days: ${{ env.ARTIFACT_RETENTION }} + if-no-files-found: error + compression-level: 0 + + tarball: + name: "build: tarball (${{ matrix.variant }})" + if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'tarball' }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - variant: full + omit_unclean: '0' + artifact_name: electrum-tarball + - variant: source-only + omit_unclean: '1' + artifact_name: electrum-sourceonly-tarball + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Build source tarball (${{ matrix.variant }}) + env: + OMIT_UNCLEAN_FILES: ${{ matrix.omit_unclean }} + run: ./contrib/build-linux/sdist/build.sh + + - name: List output + run: ls -lah dist/ + + - name: Upload tarball + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.artifact_name }}-${{ github.sha }} + path: dist/*.tar.gz + retention-days: ${{ env.ARTIFACT_RETENTION }} + if-no-files-found: error + compression-level: 0 diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml new file mode 100644 index 000000000000..ef27b54995bf --- /dev/null +++ b/.github/workflows/locale.yml @@ -0,0 +1,44 @@ +name: locale + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +jobs: + push-locale: + name: "locale: upload to crowdin" + runs-on: ubuntu-24.04 + steps: + - name: Checkout (with submodules) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + submodules: true + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.10" + cache: 'pip' + cache-dependency-path: contrib/requirements/requirements-ci.txt + + - name: Install OS deps + run: | + sudo apt-get update + sudo apt-get -y install gettext qt6-l10n-tools + + - name: Install Python deps + run: | + pip install -r contrib/requirements/requirements-ci.txt + pip install requests + + - name: Push locale to Crowdin + # CROWDIN_API_KEY needs to be set in GitHub repository settings + # - api key is for crowdin account: "SomberNight_CI_BOT" + env: + crowdin_api_key: ${{ secrets.CROWDIN_API_KEY }} + run: ./contrib/locale/push_locale.py diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml new file mode 100644 index 000000000000..90053c93b89d --- /dev/null +++ b/.github/workflows/regtest.yml @@ -0,0 +1,113 @@ +name: regtest + +on: + push: + branches: [master] + tags: ['*'] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +permissions: + contents: read + +jobs: + regtest: + name: "Regtest functional tests" + runs-on: ubuntu-24.04 + env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ + ELECTRUM_ECC_DONT_COMPILE: "1" # we build manually to make caching it easier + PIP_BREAK_SYSTEM_PACKAGES: "1" + # ElectrumX exits with an error without this: + ALLOW_ROOT: "1" + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.10" + cache: 'pip' + cache-dependency-path: contrib/requirements/requirements.txt + - name: Determine latest bitcoind version + id: bitcoind-version + run: | + BITCOIND_VERSION=$(curl https://bitcoincore.org/en/download/ | grep -E -i --only-matching 'Latest version: [0-9\.]+' | grep -E --only-matching '[0-9\.]+') + if [ -z "$BITCOIND_VERSION" ]; then + echo "Failed to detect bitcoind version from bitcoincore.org" >&2 + exit 1 + fi + echo "Detected bitcoind version: $BITCOIND_VERSION" + echo "version=$BITCOIND_VERSION" >> "$GITHUB_OUTPUT" + - name: Cache bitcoind binary + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: /tmp/bitcoind + key: bitcoind-ubuntu-24.04-${{ steps.bitcoind-version.outputs.version }} + - name: Install OS deps + run: | + sudo apt-get update + sudo apt-get -y install curl jq bc automake libtool + - name: Install Electrum and ElectrumX + # installs e-x some commits after 1.18.0 tag + # uses --ignore-installed to ignore installed system installed attrs + run: | + python3 -m pip install --user --upgrade pip + python3 -m pip install --user .[tests] --ignore-installed + python3 -m pip install --user git+https://github.com/spesmilo/electrumx.git@0b260d4345242cc41e316e97d7de10ae472fd172 + - name: Fetch bitcoind binary + env: + BITCOIND_VERSION: ${{ steps.bitcoind-version.outputs.version }} + run: | + mkdir -p /tmp/bitcoind + BITCOIND_FILENAME=bitcoin-$BITCOIND_VERSION-x86_64-linux-gnu.tar.gz + BITCOIND_PATH=/tmp/bitcoind/$BITCOIND_FILENAME + BITCOIND_URL=https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/$BITCOIND_FILENAME + cd /tmp/bitcoind + tar -xaf $BITCOIND_PATH || (rm -f /tmp/bitcoind/* && curl --output $BITCOIND_PATH $BITCOIND_URL && tar -xaf $BITCOIND_PATH) + sudo cp -a bitcoin-$BITCOIND_VERSION/* /usr/ + - name: Cache libsecp256k1 build + id: cache-libsecp + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/_saved_secp256k1_build + key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }} + - name: Build libsecp256k1 + if: steps.cache-libsecp.outputs.cache-hit != 'true' + run: | + ./contrib/make_libsecp256k1.sh + mkdir -p contrib/_saved_secp256k1_build + cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ + - name: Start bitcoind (regtest) + run: nohup tests/regtest/run_bitcoind.sh > /tmp/bitcoind.log 2>&1 & + - name: Start electrumx + run: nohup tests/regtest/run_electrumx.sh > /tmp/electrumx.log 2>&1 & + - name: Run regtest tests + run: | + sleep 10 + python3 -m unittest tests/regtest.py --failfast || TEST_EXIT_CODE=$? + tar -czf test_wallets.tar.gz /tmp/alice /tmp/bob /tmp/carol || true + exit ${TEST_EXIT_CODE:-0} + # if any test fails, the test will get aborted (--failfast) and the wallet directories will be + # available for download in the Cirrus UI + - name: Upload test wallets (on failure) + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-wallets + path: test_wallets.tar.gz + if-no-files-found: ignore + retention-days: 7 + - name: Dump service logs (on failure) + if: failure() + run: | + echo "--- bitcoind log ---" + tail -200 /tmp/bitcoind.log || true + echo "--- electrumx log ---" + tail -200 /tmp/electrumx.log || true diff --git a/.github/workflows/security-review.yml b/.github/workflows/security-review.yml new file mode 100644 index 000000000000..021ae10df4cb --- /dev/null +++ b/.github/workflows/security-review.yml @@ -0,0 +1,93 @@ +name: security-review + +on: + pull_request_target: + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to review' + required: true + type: string + +permissions: + contents: read +# pull-requests: write # required for the script to post review comments via GITHUB_TOKEN + +concurrency: + group: security-review-pr-${{ github.event.pull_request.number || inputs.pr_number }} + cancel-in-progress: true + +jobs: + security-review: + name: "security review: Claude Code" + runs-on: ubuntu-24.04 + + # Auto-run only for maintainers (and their forks). + # External contributors trigger manually so we can first review if their PR modifies + # the Python script this task calls (they could attempt to exfiltrate the api key through the script). + if: | + github.event_name == 'workflow_dispatch' || + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association) + + env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ + ELECTRUM_ECC_DONT_COMPILE: "1" + steps: + - name: Checkout PR head + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: refs/pull/${{ github.event.pull_request.number || inputs.pr_number }}/head + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + + - name: Install Claude Code CLI + run: sudo npm install -g @anthropic-ai/claude-code + + # install Python and dependencies so the llm can quickly access the dependencies source and execute code/tests + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.14' + cache: 'pip' + cache-dependency-path: | + contrib/requirements/requirements-ci.txt + contrib/requirements/requirements.txt + + - name: Cache libsecp256k1 + id: cache-libsecp + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/_saved_secp256k1_build + key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }} + + - name: Build libsecp256k1 + if: steps.cache-libsecp.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get -y install automake libtool + ./contrib/make_libsecp256k1.sh + mkdir -p contrib/_saved_secp256k1_build + cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ + + - name: Install Qt/QML runtime deps + run: | + sudo apt-get update + sudo apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3 + + - name: Install Python dependencies + run: | + pip install -r contrib/requirements/requirements-ci.txt + pip install ".[tests,qml_gui]" + + - name: Run Claude Code security review + env: + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} + BASE_BRANCH: ${{ github.event.pull_request.base.ref || 'master' }} + # needs to be set in the GitHub repository settings + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # can be enabled to make claude comment on PRs + run: python3 contrib/ci/claude_security_review.py diff --git a/.github/workflows/submodules.yml b/.github/workflows/submodules.yml new file mode 100644 index 000000000000..9ae2996b8c6f --- /dev/null +++ b/.github/workflows/submodules.yml @@ -0,0 +1,22 @@ +name: check-submodules + +on: + push: + tags: ['*'] + +permissions: + contents: read + +jobs: + check-submodules: + runs-on: ubuntu-24.04 + steps: + - name: Checkout (with submodules and tags) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + submodules: true + - name: Fetch all tags + run: git fetch --all --tags + - name: Run check_submodules.sh + run: ./contrib/deterministic-build/check_submodules.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000000..27097e82ef88 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,207 @@ +name: tests + +on: + push: + branches: [master] + tags: ['*'] + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +permissions: + contents: read + +jobs: + flake8-mandatory: + name: "linter: Flake8 Mandatory" + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.10" + cache: 'pip' + - name: Install flake8 + run: pip install "flake8==7.3.0" "flake8-bugbear==25.10.21" + - name: Run flake8 + # list of error codes: + # - https://flake8.pycqa.org/en/latest/user/error-codes.html + # - https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes + # - https://github.com/PyCQA/flake8-bugbear/tree/8c0e7eb04217494d48d0ab093bf5b31db0921989#list-of-warnings + run: | + flake8 . --count \ + --select="E9,E101,E129,E273,E274,E703,E71,E722,F5,F6,F7,F8,W191,W29,B,B909" \ + --ignore="B007,B009,B010,B036,B042,F541,F841" \ + --show-source --statistics \ + --exclude "*_pb2.py,electrum/_vendor/" + +# Disabled because it lets the PRs CI run appear failed and the codebase isn't +# adapted to these lints (so it will always fail). +# flake8-nonmandatory: +# name: "linter: Flake8 Non-Mandatory" +# runs-on: ubuntu-24.04 +# continue-on-error: true +# steps: +# - name: Checkout +# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - name: Set up Python +# uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 +# with: +# python-version: "3.10" +# cache: 'pip' +# - name: Install flake8 +# run: pip install "flake8==7.3.0" "flake8-bugbear==25.10.21" +# - name: Run flake8 +# run: | +# flake8 . --count \ +# --select="E,F,W,C90,B" \ +# --ignore="" \ +# --show-source --statistics \ +# --exclude "*_pb2.py,electrum/_vendor/" + + ban-unicode: + name: "linter: ban unicode" + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.10" + - name: Run ban_unicode + run: ./contrib/ban_unicode.py + + # unittests using the 'latest' runtime python-dependencies + unittests: + name: "unittests: py${{ matrix.python }}${{ matrix.debug && ', debug-mode' || '' }}" + runs-on: ubuntu-24.04 + needs: [flake8-mandatory] + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + debug: [false] + include: + - python: "3.14" + debug: true + env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ + PYTHONASYNCIODEBUG: ${{ matrix.debug && '1' || '' }} + PYTHONDEVMODE: ${{ matrix.debug && '1' || '' }} + ELECTRUM_ECC_DONT_COMPILE: "1" + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # full clone for coveralls + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + cache-dependency-path: | + contrib/requirements/requirements-ci.txt + contrib/requirements/requirements.txt + - name: Cache libsecp256k1 + id: cache-libsecp + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/_saved_secp256k1_build + key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }} + - name: Build libsecp256k1 + if: steps.cache-libsecp.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get -y install automake libtool + ./contrib/make_libsecp256k1.sh + mkdir -p contrib/_saved_secp256k1_build + cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ + - name: Install Qt/QML runtime deps + run: | + sudo apt-get update + sudo apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3 + - name: Install Python dependencies + run: | + pip install -r contrib/requirements/requirements-ci.txt + pip install ".[tests,qml_gui]" + - name: Log versions + run: python3 --version && pip freeze --all + - name: Run pytest with coverage + run: | + coverage run --source=electrum \ + "--omit=electrum/gui/*,electrum/plugins/*,electrum/scripts/*" \ + -m pytest tests -v + coverage report + - name: Upload to Coveralls + if: matrix.python == '3.10' && !matrix.debug + env: + # 'COVERALLS_REPO_TOKEN' needs to be set in the GitHub repository settings + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + CI_NAME: github-actions + CI_BUILD_NUMBER: ${{ github.run_id }} + CI_JOB_ID: ${{ github.job }}-${{ github.run_attempt }} + CI_BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + CI_BRANCH: ${{ github.ref_name }} + CI_PULL_REQUEST: ${{ github.event.pull_request.number }} + # the repo token will be empty when pull requests from forks get opened + # so we won't upload on every pull request, but it will run again + # with the token once the PR gets merged. + run: if [ -n "$COVERALLS_REPO_TOKEN" ]; then coveralls; fi + + # unittests using the ~same frozen dependencies that are used in the released binaries + # note: not using pinned pyqt here, due to "qml_gui" extra + unittests-frozen: + name: "unittests: py3.10, frozen-deps" + runs-on: ubuntu-24.04 + needs: [flake8-mandatory] + env: + LD_LIBRARY_PATH: contrib/_saved_secp256k1_build/ + ELECTRUM_ECC_DONT_COMPILE: "1" + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.10" + cache: 'pip' + cache-dependency-path: | + contrib/requirements/requirements-ci.txt + contrib/requirements/requirements.txt + contrib/deterministic-build/requirements.txt + contrib/deterministic-build/requirements-binaries.txt + contrib/deterministic-build/requirements-build-base.txt + - name: Cache libsecp256k1 + id: cache-libsecp + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: contrib/_saved_secp256k1_build + key: libsecp-${{ runner.os }}-${{ hashFiles('contrib/make_libsecp256k1.sh') }} + - name: Build libsecp256k1 + if: steps.cache-libsecp.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get -y install automake libtool + ./contrib/make_libsecp256k1.sh + mkdir -p contrib/_saved_secp256k1_build + cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ + - name: Install Qt/QML runtime deps + run: | + sudo apt-get update + sudo apt-get -y install libgl1 libegl1 libxkbcommon0 libdbus-1-3 + - name: Install Python dependencies (frozen) + run: | + pip install -r contrib/deterministic-build/requirements-build-base.txt + pip install -r contrib/requirements/requirements-ci.txt + pip install -r contrib/deterministic-build/requirements.txt -r contrib/deterministic-build/requirements-binaries.txt + pip install ".[tests,qml_gui]" + - name: Log versions + run: python3 --version && pip freeze --all + - name: Run pytest + run: pytest tests -v diff --git a/README.md b/README.md index 500f2eccb654..6fa0caccd1c9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Language: Python (>= 3.10) Homepage: https://electrum.org/ ``` -[![Build Status](https://api.cirrus-ci.com/github/spesmilo/electrum.svg?branch=master)](https://cirrus-ci.com/github/spesmilo/electrum) +[![Build Status](https://github.com/spesmilo/electrum/actions/workflows/builds.yml/badge.svg?branch=master)](https://github.com/spesmilo/electrum/actions/workflows/builds.yml) [![Test coverage statistics](https://coveralls.io/repos/github/spesmilo/electrum/badge.svg?branch=master)](https://coveralls.io/github/spesmilo/electrum?branch=master) [![Help translate Electrum online](https://d322cqt584bo4o.cloudfront.net/electrum/localized.svg)](https://crowdin.com/project/electrum) diff --git a/contrib/android/Readme.md b/contrib/android/Readme.md index 04f92d0e601b..c378bff20c10 100644 --- a/contrib/android/Readme.md +++ b/contrib/android/Readme.md @@ -203,10 +203,9 @@ cat d ### How to install apks built by the CI on my phone? -The CI (Cirrus) builds apks on most git commits. -See e.g. [here](https://github.com/spesmilo/electrum/runs/9272252577). -The task name should start with "Android build". -Click "View more details on Cirrus CI" to get to cirrus' website, and search for "Artifacts". +The CI (GitHub Actions) builds apks on a nightly schedule. +See the [`builds` workflow](https://github.com/spesmilo/electrum/actions/workflows/builds.yml). +Open the run of interest and download the `electrum-android-*` artifact. The apk is built in `debug` mode, and is signed using an ephemeral RSA key. For tech demo purposes, you can directly install this apk on your phone. diff --git a/contrib/build_tools_util.sh b/contrib/build_tools_util.sh index 2dd9b8baf133..1e8d9c209488 100755 --- a/contrib/build_tools_util.sh +++ b/contrib/build_tools_util.sh @@ -172,12 +172,7 @@ fi export GCC_STRIP_BINARIES="${GCC_STRIP_BINARIES:-0}" -if [ -n "$CIRRUS_CPU" ] ; then - # special-case for CI. see https://github.com/cirruslabs/cirrus-ci-docs/issues/1115 - export CPU_COUNT="$CIRRUS_CPU" -else - export CPU_COUNT="$(nproc 2> /dev/null || sysctl -n hw.ncpu)" -fi +export CPU_COUNT="$(nproc 2> /dev/null || sysctl -n hw.ncpu)" info "Found $CPU_COUNT CPUs, which we might use for building." diff --git a/contrib/ci/claude_security_review.py b/contrib/ci/claude_security_review.py index ea1de7e2292e..a0c4d646c2a3 100755 --- a/contrib/ci/claude_security_review.py +++ b/contrib/ci/claude_security_review.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Cirrus CI task: Claude Code security review for Electrum pull requests. +GitHub Actions job: Claude Code security review for Electrum pull requests. Runs Claude Code against the PR diff to detect critical security vulnerabilities. Optionally posts findings as a GitHub PR comment. @@ -15,11 +15,13 @@ CLAUDE_CODE_OAUTH_TOKEN -- OAuth token from `claude setup-token` (MAX subscription) Optional: GITHUB_TOKEN -- GitHub token for posting PR comments - Set by Cirrus CI: - CIRRUS_PR -- PR number (empty if not a PR build) - CIRRUS_BASE_BRANCH -- target branch of the PR - CIRRUS_REPO_FULL_NAME -- e.g. "spesmilo/electrum" - CIRRUS_TASK_ID -- current Cirrus task ID + Set by the workflow: + PR_NUMBER -- PR number (empty if not a PR build) + BASE_BRANCH -- target branch of the PR + Set by GitHub Actions runtime: + GITHUB_REPOSITORY -- e.g. "spesmilo/electrum" + GITHUB_RUN_ID -- current workflow run ID + GITHUB_SERVER_URL -- e.g. "https://github.com" """ import json @@ -147,8 +149,9 @@ def post_github_comment(body: str, *, repo: str, pr: str) -> None: print("GITHUB_TOKEN not set -- skipping PR comment.") return - task_id = os.environ.get("CIRRUS_TASK_ID", "") - log_url = f"https://cirrus-ci.com/task/{task_id}" if task_id else "" + run_id = os.environ.get("GITHUB_RUN_ID", "") + server_url = os.environ.get("GITHUB_SERVER_URL", "https://github.com") + log_url = f"{server_url}/{repo}/actions/runs/{run_id}" if run_id else "" comment = ( f"## Security Review -- Issues Found\n\n" @@ -191,17 +194,16 @@ def main() -> int: print("Claude Code Security Review") print(separator) - pr = os.environ.get("CIRRUS_PR", "").strip() + pr = os.environ.get("PR_NUMBER", "").strip() if not pr: - print("Not a PR build (CIRRUS_PR is empty). Skipping.") + print("Not a PR build (PR_NUMBER is empty). Skipping.") return 0 if not os.environ.get("CLAUDE_CODE_OAUTH_TOKEN", "").strip(): print("ERROR: CLAUDE_CODE_OAUTH_TOKEN is not set.") return 2 - repo = os.environ.get("CIRRUS_REPO_FULL_NAME", "").strip() - base_branch = os.environ.get("CIRRUS_BASE_BRANCH", "master").strip() + base_branch = os.environ.get("BASE_BRANCH", "master").strip() print(f"PR #{pr} -> base branch: {base_branch}") print("\nFetching base branch...") @@ -254,6 +256,7 @@ def main() -> int: verdict = parse_verdict(review) if verdict == VERDICT_FAIL: + repo = os.environ.get("GITHUB_REPOSITORY", "").strip() print("\nVERDICT: FAIL -- Critical or high severity issues found.") post_github_comment(review, repo=repo, pr=pr) return 1