name: build on: push: branches: - master - stable tags: - "v*" - "!*-fdroid" - "!*-armv7a" pull_request: paths: - "src/**" - "apps/simplex-chat/**" - "apps/simplex-bot/**" - "apps/simplex-bot-advanced/**" - "apps/simplex-badge-service/**" - "apps/simplex-broadcast-bot/**" - "apps/simplex-directory-service/**" - "tests/**" - "bots/src/**" - "simplex-chat.cabal" - "cabal.project" - "Dockerfile*" - "scripts/ci/**" - "scripts/desktop/**" - ".github/**" env: opt: ${{ github.event_name == 'pull_request' && 'O0' || 'O2' }} ghc_options: ${{ github.event_name == 'pull_request' && '--ghc-options=-O0' || '' }} release: ${{ startsWith(github.ref, 'refs/tags/v') }} branch: ${{ github.base_ref || github.ref_name }} cache_o0: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable') }} release_branch: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable') && github.ref_name || '' }} ZSTD_CLEVEL: 9 # This workflow uses custom actions (prepare-build and prepare-release) defined in: # # .github/actions/ # ├── prepare-build # │ └── action.yml # └── prepare-release # └── action.yml # Important! # Do not use always(), it makes build unskippable. # See: https://github.com/actions/runner/issues/1846#issuecomment-1246102753 jobs: # ============================= # Global variables # ============================= # That is the only and less hacky way to setup global variables # to use in strategy matrix (env:/YAML anchors doesn't work). # See: https://github.com/orgs/community/discussions/56787#discussioncomment-6041789 # https://github.com/actions/runner/issues/1182 # https://stackoverflow.com/a/77549656 variables: runs-on: ubuntu-latest outputs: GHC_VER: 9.6.3 JAVA_VER: 17 steps: - name: Dummy job when we have just simple variables if: false run: echo # ============================= # Create release # ============================= # Create release, but only if it's triggered by tag push. # On pushes to master or stable: claim the simplex-chat-libs release core- for the branch # and create its draft, create the draft of a missing add-on release, and find the package versions to publish. # On pull requests and other pushes, this job will always complete. # Secrets: # ------- # NODEJS_REPO_TOKEN # Only select repositories: simplex-chat-libs # Permissions: # * Contents (Read and Write) maybe-release: runs-on: ubuntu-latest outputs: libs_tag: ${{ steps.libs.outputs.tag }} core_changed: ${{ steps.core.outputs.core }} addon_tag: ${{ steps.addon.outputs.tag }} addon_draft: ${{ steps.addon.outputs.draft }} libs_latest: ${{ steps.latest_libs.outputs.tag }} publish_types: ${{ steps.libs.outputs.tag && steps.publish.outputs.types_version || '' }} publish_nodejs: ${{ steps.publish.outputs.nodejs_version }} publish_python: ${{ steps.publish.outputs.python_version }} publish: ${{ steps.publish.outputs.nodejs_version || steps.publish.outputs.python_version }} steps: - name: Clone project if: github.event_name == 'push' || github.event_name == 'pull_request' uses: actions/checkout@v3 with: fetch-depth: 2 - name: Check chat core change id: core if: github.event_name == 'pull_request' env: BASE: ${{ github.event.pull_request.base.sha }} run: | git fetch -q --depth=1 origin "$BASE" python3 scripts/ci/packages_release.py changes "$BASE" | grep '^core=' >> "$GITHUB_OUTPUT" - name: Build changelog id: build_changelog if: startsWith(github.ref, 'refs/tags/v') uses: simplex-chat/release-changelog-builder-action@v5 with: configuration: .github/changelog_conf.json failOnError: true ignorePreReleases: true commitMode: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create release if: startsWith(github.ref, 'refs/tags/v') uses: simplex-chat/action-gh-release@v2 with: body: ${{ steps.build_changelog.outputs.changelog }} prerelease: true files: | LICENSE fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Library release id: libs if: env.release_branch shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} LIBS_REPOSITORY: ${{ github.repository }}-libs run: | version=$(sed -n 's/^version: *//p' simplex-chat.cabal) tag=core-$version claim_output=$(python3 scripts/ci/packages_release.py claim-core "$version" "$GITHUB_SHA" "$release_branch") eval "$claim_output" if [ "$claim" = skip ]; then echo "::notice title=Library release::core $version is recorded for another commit" exit 0 fi status_output=$(python3 scripts/ci/packages_release.py release-status "$tag") eval "$status_output" if [ "$status" = absent ]; then gh release create "$tag" --repo "$LIBS_REPOSITORY" --draft --title "$tag" --notes "" ${commit:+--target "$commit"} fi if [ "$status" != published ]; then echo "tag=$tag" >> "$GITHUB_OUTPUT" fi - name: Add-on release id: addon if: env.release_branch shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} LIBS_REPOSITORY: ${{ github.repository }}-libs run: | addon_output=$(python3 scripts/ci/packages_release.py addon-release) eval "$addon_output" if [ "$status" = absent ]; then tag=nodejs-addon-${GITHUB_SHA::7} gh release create "$tag" --repo "$LIBS_REPOSITORY" --draft --title "$tag" --notes "$notes" fi if [ "$status" != published ]; then echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "draft=true" >> "$GITHUB_OUTPUT" fi - name: Latest library id: latest_libs if: steps.addon.outputs.draft env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} LIBS_REPOSITORY: ${{ github.repository }}-libs run: python3 scripts/ci/packages_release.py latest-libs --branch "$release_branch" >> "$GITHUB_OUTPUT" - name: Versions to publish id: publish if: env.release_branch env: GH_TOKEN: ${{ github.token }} LIBS_REPOSITORY: ${{ github.repository }}-libs NPM_SCOPE: ${{ vars.NPM_SCOPE }} PYPI_PROJECT: ${{ vars.PYPI_PROJECT }} PYPI_URL: ${{ vars.PYPI_URL }} run: python3 scripts/ci/packages_release.py publish-plan >> "$GITHUB_OUTPUT" # ========================= # Linux Build # ========================= build-linux: name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] runs-on: ${{ matrix.runner }} permissions: contents: write packages: write env: image_cache: ghcr.io/simplex-chat/simplex-chat-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }} source_hashes: dist-newstyle/sources.sha256 concurrency: &linux-concurrency group: ${{ github.workflow }}-${{ github.ref }}-linux-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.ghc }}-${{ needs.maybe-release.outputs.libs_tag }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} strategy: fail-fast: false matrix: include: - os: 22.04 os_underscore: 22_04 arch: x86_64 runner: "ubuntu-22.04" should_run: true ghc: ${{ needs.variables.outputs.GHC_VER }} hash: 'sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5' libs: true - os: 24.04 os_underscore: 24_04 arch: x86_64 runner: "ubuntu-24.04" should_run: true ghc: ${{ needs.variables.outputs.GHC_VER }} hash: 'sha256:98ff7968124952e719a8a69bb3cccdd217f5fe758108ac4f21ad22e1df44d237' steps: &linux-steps - name: Checkout Code if: matrix.should_run == true uses: actions/checkout@v3 with: submodules: recursive - name: Setup swap if: matrix.ghc == '8.10.7' && matrix.should_run == true uses: ./.github/actions/swap with: swap-size-gb: 30 - name: Get UID and GID id: ids run: | echo "uid=$(id -u)" >> $GITHUB_OUTPUT echo "gid=$(id -g)" >> $GITHUB_OUTPUT # Otherwise we run out of disk space with Docker build - name: Free disk space if: matrix.should_run == true shell: bash run: ./scripts/ci/linux_util_free_space.sh - name: Set up Docker Buildx if: matrix.should_run == true uses: simplex-chat/docker-setup-buildx-action@v3 - name: Log in to GitHub Container Registry if: matrix.should_run == true && env.release != 'true' shell: bash env: GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: echo "$GHCR_TOKEN" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - name: Compute image cache week id: week if: matrix.should_run == true && env.release != 'true' run: echo "week=$(date -u +%G-W%V)" >> "$GITHUB_OUTPUT" - name: Build and cache Docker image if: matrix.should_run == true uses: simplex-chat/docker-build-push-action@v6 with: context: . load: true file: Dockerfile.build tags: build/${{ matrix.os }}:latest build-args: | TAG=${{ matrix.os }} HASH=${{ matrix.hash }} GHC=${{ matrix.ghc }} USER_UID=${{ steps.ids.outputs.uid }} USER_GID=${{ steps.ids.outputs.gid }} cache-from: ${{ env.release != 'true' && format('type=registry,ref={0}-{1}', env.image_cache, steps.week.outputs.week) || '' }} cache-to: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && format('type=registry,ref={0}-{1},mode=max,ignore-error=true', env.image_cache, steps.week.outputs.week) || '' }} # Docker needs these flags for AppImage build: # --device /dev/fuse # --cap-add SYS_ADMIN # --security-opt apparmor:unconfined - name: Start container if: matrix.should_run == true shell: bash run: | mkdir -p ~/.cabal docker run -t -d \ --device /dev/fuse \ --cap-add SYS_ADMIN \ --security-opt apparmor:unconfined \ --name builder \ -e CABAL_DIR=/home/builder/.cabal \ -v ~/.cabal:/home/builder/.cabal \ -v /home/runner/work/_temp:/home/runner/work/_temp \ -v ${{ github.workspace }}:/project \ build/${{ matrix.os }}:latest - name: Compute cache key prefixes id: keys if: matrix.should_run == true && env.release != 'true' run: | toolchain=$(docker exec builder dpkg-query -W -f '${db:Status-Status} ${binary:Package} ${Version}\n' 'binutils*' 'cpp*' 'gcc*' '*-dev' | grep '^installed ' | sha256sum | cut -c1-16) target=ubuntu-${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}-$toolchain echo "deps=deps-$target" >> "$GITHUB_OUTPUT" echo "dist=dist-$target" >> "$GITHUB_OUTPUT" echo "dist_lib=dist-lib-$target" >> "$GITHUB_OUTPUT" - name: Restore dependencies from cache id: restore-deps if: matrix.should_run == true && env.release != 'true' uses: actions/cache/restore@v4 with: path: ~/.cabal/store key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.deps }}-${{ env.branch }}- ${{ steps.keys.outputs.deps }}- - name: Warn if dependencies not in cache if: matrix.should_run == true && env.release != 'true' && steps.restore-deps.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.deps }}-${{ env.branch }}" - name: Restore source repositories from cache id: restore-src if: matrix.should_run == true && env.release != 'true' uses: actions/cache/restore@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} restore-keys: src-${{ runner.os }}- - name: Warn if source repositories not in cache if: matrix.should_run == true && env.release != 'true' && steps.restore-src.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::src-${{ runner.os }}" - name: Restore build directory from cache id: restore-build if: matrix.should_run == true && env.release != 'true' uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-${{ env.opt }}- - name: Warn if build directory not in cache if: matrix.should_run == true && env.release != 'true' && steps.restore-build.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}" - name: Restore library build directory from cache id: restore-build-lib if: env.release != 'true' && matrix.libs && needs.maybe-release.outputs.core_changed && matrix.arch == 'x86_64' uses: actions/cache/restore@v4 with: path: | dist-lib/* !dist-lib/src key: ${{ steps.keys.outputs.dist_lib }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist_lib }}-${{ env.branch }}- ${{ steps.keys.outputs.dist_lib }}- - name: Warn if library build directory not in cache if: steps.restore-build-lib.outcome == 'success' && steps.restore-build-lib.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.dist_lib }}-${{ env.branch }}" - name: Set old timestamps on unchanged sources (GHC 8.10.7) if: matrix.ghc == '8.10.7' && steps.restore-build.outputs.cache-matched-key != '' && hashFiles(env.source_hashes) != '' run: scripts/ci/source-timestamps.sh restore ${{ env.source_hashes }} - name: Prepare cabal.project.local if: matrix.should_run == true shell: bash run: | echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - name: Build CLI id: build-cli if: matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | scripts/ci/prune-old-builds.sh dist-newstyle cabal update cabal build -j --enable-tests ${{ env.ghc_options }} mkdir -p /out for i in simplex-chat simplex-chat-test; do bin=$(find /project/dist-newstyle -name "$i" -type f -executable) chmod +x "$bin" cp "$bin" /out/ done strip /out/simplex-chat # Also for pull requests that change the chat core, for the package tests. - name: Build library id: build-lib if: matrix.libs && (needs.maybe-release.outputs.libs_tag || (needs.maybe-release.outputs.core_changed && matrix.arch == 'x86_64')) shell: docker exec -t builder sh -eu {0} run: | scripts/ci/prune-old-builds.sh dist-lib mkdir -p dist-lib rm -rf dist-lib/src ln -s ../dist-newstyle/src dist-lib/src DIST_DIR=dist-lib SKIP_VLC=1 scripts/desktop/build-lib-linux.sh name=simplex-chat-libs-linux-${{ matrix.arch }} mkdir -p release-libs/$name/libs find apps/multiplatform/common/src/commonMain/cpp/desktop/libs/linux-${{ matrix.arch }} -maxdepth 1 -type f -exec cp {} release-libs/$name/libs/ \; (cd release-libs/$name && zip -r ../$name.zip libs) - name: Record source hashes (GHC 8.10.7) if: matrix.should_run == true && matrix.ghc == '8.10.7' shell: docker exec -t builder sh -eu {0} run: scripts/ci/source-timestamps.sh record ${{ env.source_hashes }} - name: Compute dependency cache key id: hash-deps if: matrix.should_run == true && env.release != 'true' shell: bash run: echo "hash=$(ls ~/.cabal/store/ghc-*/ | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" - name: Save dependencies to cache if: matrix.should_run == true && env.release != 'true' && steps.restore-deps.outputs.cache-matched-key != format('{0}-{1}-{2}', steps.keys.outputs.deps, env.branch, steps.hash-deps.outputs.hash) uses: actions/cache/save@v4 with: path: ~/.cabal/store key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ steps.hash-deps.outputs.hash }} - name: Save source repositories to cache if: matrix.should_run == true && env.release != 'true' && matrix.libs && matrix.arch == 'x86_64' && steps.restore-src.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} - name: Save build directory to cache if: matrix.should_run == true && env.release != 'true' && steps.restore-build.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} - name: Save library build directory to cache if: steps.build-lib.outcome == 'success' && env.release != 'true' && steps.restore-build-lib.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | dist-lib/* !dist-lib/src key: ${{ steps.keys.outputs.dist_lib }}-${{ env.branch }}-${{ github.sha }} - name: Upload library if: needs.maybe-release.outputs.libs_tag && matrix.libs shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} TAG: ${{ needs.maybe-release.outputs.libs_tag }} run: gh release upload "$TAG" release-libs/*.zip --clobber --repo "${{ github.repository }}-libs" - name: Build CLI deb if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | version=${{ github.ref }} version=${version#refs/tags/v} version=${version%-*} ./scripts/desktop/build-cli-deb.sh "$version" - name: Copy tests from container if: matrix.should_run == true shell: bash run: | docker cp builder:/out/simplex-chat-test . - name: Copy CLI from container and prepare it id: linux_cli_prepare if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | cli_name="simplex-chat-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}" cli_deb_name="${cli_name}.deb" cli_path="${{ github.workspace }}" docker cp builder:/out/simplex-chat "./${cli_name}" docker cp builder:/out/deb-build/simplex-chat.deb "./${cli_deb_name}" echo "bin_name=${cli_name}" >> $GITHUB_OUTPUT echo "bin_path=${cli_path}/${cli_name}" >> $GITHUB_OUTPUT echo "bin_hash=$(echo SHA2-256\(${cli_name}\)= $(openssl sha256 "${cli_path}/${cli_name}" | cut -d' ' -f 2))" >> $GITHUB_OUTPUT echo "deb_name=${cli_deb_name}" >> $GITHUB_OUTPUT echo "deb_path=${cli_path}/${cli_deb_name}" >> $GITHUB_OUTPUT echo "deb_hash=$(echo SHA2-256\(${cli_deb_name}\)= $(openssl sha256 "${cli_path}/${cli_deb_name}" | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_name: ${{ steps.linux_cli_prepare.outputs.bin_name }} bin_path: ${{ steps.linux_cli_prepare.outputs.bin_path }} bin_hash: ${{ steps.linux_cli_prepare.outputs.bin_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload CLI deb if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_name: ${{ steps.linux_cli_prepare.outputs.deb_name }} bin_path: ${{ steps.linux_cli_prepare.outputs.deb_path }} bin_hash: ${{ steps.linux_cli_prepare.outputs.deb_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build Desktop if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | export ASSETS_DIR='../../assets' scripts/desktop/make-deb-linux.sh - name: Prepare Desktop id: linux_desktop_build if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: bash run: | path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/deb/simplex_${{ matrix.arch }}.deb ) echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-256\(simplex-desktop-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}.deb\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop uses: ./.github/actions/prepare-release if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true with: bin_path: ${{ steps.linux_desktop_build.outputs.package_path }} bin_name: simplex-desktop-ubuntu-${{ matrix.os_underscore }}-${{ matrix.arch }}.deb bin_hash: ${{ steps.linux_desktop_build.outputs.package_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build AppImage if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | export ASSETS_DIR='../../assets' scripts/desktop/make-appimage-linux.sh - name: Prepare AppImage id: linux_appimage_build if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: bash run: | path=$(echo ${{ github.workspace }}/apps/multiplatform/release/main/*imple*.AppImage) echo "appimage_path=$path" >> $GITHUB_OUTPUT echo "appimage_hash=$(echo SHA2-256\(simplex-desktop-${{ matrix.arch }}.AppImage\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload AppImage if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.linux_appimage_build.outputs.appimage_path }} bin_name: "simplex-desktop-${{ matrix.arch }}.AppImage" bin_hash: ${{ steps.linux_appimage_build.outputs.appimage_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Mount RAM disk for tests if: matrix.should_run == true && matrix.arch == 'x86_64' shell: bash run: | mkdir -p tests/tmp sudo mount -t tmpfs -o size=512M tmpfs tests/tmp sudo chown $(id -u):$(id -g) tests/tmp - name: Run tests if: matrix.should_run == true && matrix.arch == 'x86_64' timeout-minutes: 120 shell: bash run: | scripts/ci/test-watchdog.sh 45 & watchdog=$! i=1 attempts=1 ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 while [ "$i" -le "$attempts" ]; do if ./simplex-chat-test +RTS -s -RTS; then break else echo "Attempt $i failed, retrying..." i=$((i + 1)) sleep 1 fi done kill "$watchdog" || true if [ "$i" -gt "$attempts" ]; then echo "All "$attempts" attempts failed." exit 1 fi - name: Setup Node.js id: setup-node if: ${{ !cancelled() && matrix.libs && (github.event_name == 'pull_request' || needs.maybe-release.outputs.addon_draft || needs.maybe-release.outputs.publish) }} uses: actions/setup-node@v7 with: node-version: 22 - name: Setup Python id: setup-python if: ${{ !cancelled() && steps.setup-node.outcome == 'success' }} uses: actions/setup-python@v7 with: python-version: "3.12" - name: Package tests (pull request) if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && github.event_name == 'pull_request' && steps.build-lib.outcome != 'failure' }} timeout-minutes: 30 shell: bash env: SIMPLEX_LIBS_DIR: ${{ steps.build-lib.outcome == 'success' && format('{0}/release-libs/simplex-chat-libs-linux-{1}/libs', github.workspace, matrix.arch) || '' }} run: | scripts/ci/package-tests.sh build linux-${{ matrix.arch }} scripts/ci/package-tests.sh test - name: Add-on if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.addon_draft }} timeout-minutes: 30 shell: bash env: LIBS_RELEASE: ${{ needs.maybe-release.outputs.libs_latest }} ADDON_TAG: ${{ needs.maybe-release.outputs.addon_tag }} LIBS_REPOSITORY: ${{ github.repository }}-libs GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} run: scripts/ci/package-tests.sh addon linux-${{ matrix.arch }} - name: Package tests id: package-tests if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.publish }} timeout-minutes: 30 shell: bash run: | scripts/ci/package-tests.sh publish linux-${{ matrix.arch }} ${{ needs.maybe-release.outputs.publish_nodejs && 'nodejs' || '' }} ${{ needs.maybe-release.outputs.publish_python && 'python' || '' }} echo "linux-${{ matrix.arch }}" > "$RUNNER_TEMP/package-tests" - name: Package tests result if: ${{ !cancelled() && steps.package-tests.outcome == 'success' }} uses: actions/upload-artifact@v7 with: name: package-tests-linux-${{ matrix.arch }} path: ${{ runner.temp }}/package-tests - name: Remove -O2 build directory id: remove-o2 if: env.cache_o0 == 'true' && !cancelled() && steps.build-cli.outcome == 'success' shell: docker exec -t builder sh -eu {0} run: find dist-newstyle -mindepth 1 -maxdepth 1 ! -name src -exec rm -rf {} + - name: Restore -O0 build directory from cache id: restore-build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-O0- - name: Set old timestamps on unchanged sources for -O0 build (GHC 8.10.7) if: ${{ !cancelled() && matrix.ghc == '8.10.7' && steps.restore-build-o0.outputs.cache-matched-key != '' && hashFiles(env.source_hashes) != '' }} run: scripts/ci/source-timestamps.sh restore ${{ env.source_hashes }} - name: Build at -O0 for pull request caches id: build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} shell: docker exec -t builder sh -eu {0} run: | scripts/ci/prune-old-builds.sh dist-newstyle cabal build -j --enable-tests --ghc-options=-O0 - name: Record source hashes for -O0 build (GHC 8.10.7) if: ${{ !cancelled() && matrix.ghc == '8.10.7' && steps.build-o0.outcome == 'success' }} shell: docker exec -t builder sh -eu {0} run: scripts/ci/source-timestamps.sh record ${{ env.source_hashes }} - name: Save -O0 build directory to cache if: ${{ !cancelled() && steps.build-o0.outcome == 'success' && steps.restore-build-o0.outputs.cache-hit != 'true' }} uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} build-linux-push: name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] if: github.event_name != 'pull_request' runs-on: ${{ matrix.runner }} permissions: contents: write packages: write env: image_cache: ghcr.io/simplex-chat/simplex-chat-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }} source_hashes: dist-newstyle/sources.sha256 cache_o0: false concurrency: *linux-concurrency strategy: fail-fast: false matrix: include: - os: 22.04 os_underscore: 22_04 arch: aarch64 runner: "ubuntu-22.04-arm" should_run: true ghc: ${{ needs.variables.outputs.GHC_VER }} hash: 'sha256:6a62a4157b8775eaf4959cb629e757d32d39d1f4c8ac1b0ddc2510b555cf72f3' libs: true - os: 24.04 os_underscore: 24_04 arch: aarch64 runner: "ubuntu-24.04-arm" should_run: true ghc: ${{ needs.variables.outputs.GHC_VER }} hash: 'sha256:68434214381cb38287104e629fe8ee720167dd98cbb36ab1cbbab342515fa6ab' steps: *linux-steps build-linux-ghc8: name: "ubuntu-${{ matrix.os }}-${{ matrix.arch }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] if: ${{ !(github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} runs-on: ${{ matrix.runner }} permissions: contents: write packages: write env: image_cache: ghcr.io/simplex-chat/simplex-chat-build-cache:${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }} source_hashes: dist-newstyle/sources.sha256 concurrency: *linux-concurrency strategy: fail-fast: false matrix: include: - os: 22.04 os_underscore: 22_04 arch: x86_64 runner: "ubuntu-22.04" ghc: "8.10.7" hash: 'sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5' should_run: true steps: *linux-steps # ================================= # Linux PostgreSQL Library Build # ================================= build-linux-postgres: name: "ubuntu-22.04-x86_64 (Postgres lib), GHC: ${{ needs.variables.outputs.GHC_VER }}" needs: [maybe-release, variables] runs-on: ubuntu-22.04 if: needs.maybe-release.outputs.libs_tag steps: - name: Checkout Code uses: actions/checkout@v3 - name: Get UID and GID id: ids run: | echo "uid=$(id -u)" >> $GITHUB_OUTPUT echo "gid=$(id -g)" >> $GITHUB_OUTPUT - name: Free disk space shell: bash run: ./scripts/ci/linux_util_free_space.sh - name: Set up Docker Buildx uses: simplex-chat/docker-setup-buildx-action@v3 - name: Build and cache Docker image uses: simplex-chat/docker-build-push-action@v6 with: context: . load: true file: Dockerfile.build tags: build/22.04:latest build-args: | TAG=22.04 HASH=sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5 GHC=${{ needs.variables.outputs.GHC_VER }} USER_UID=${{ steps.ids.outputs.uid }} USER_GID=${{ steps.ids.outputs.gid }} - name: Start container shell: bash run: | mkdir -p ~/.cabal docker run -t -d \ --name builder \ -e CABAL_DIR=/home/builder/.cabal \ -v ~/.cabal:/home/builder/.cabal \ -v /home/runner/work/_temp:/home/runner/work/_temp \ -v ${{ github.workspace }}:/project \ build/22.04:latest - name: Compute cache key prefix id: keys run: | toolchain=$(docker exec builder dpkg-query -W -f '${db:Status-Status} ${binary:Package} ${Version}\n' 'binutils*' 'cpp*' 'gcc*' '*-dev' | grep '^installed ' | sha256sum | cut -c1-16) echo "deps=deps-ubuntu-22.04-x86_64-postgres-ghc${{ needs.variables.outputs.GHC_VER }}-$toolchain" >> "$GITHUB_OUTPUT" - name: Restore dependencies from cache id: restore-deps uses: actions/cache/restore@v4 with: path: ~/.cabal/store key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.deps }}-${{ env.branch }}- ${{ steps.keys.outputs.deps }}- - name: Warn if dependencies not in cache if: steps.restore-deps.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.deps }}-${{ env.branch }}" - name: Prepare cabal.project.local shell: bash run: | echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - name: Build postgres library shell: docker exec -t builder sh -eu {0} run: | cabal update scripts/desktop/build-lib-linux.sh postgres - name: Compute dependency cache key id: hash-deps shell: bash run: echo "hash=$(ls ~/.cabal/store/ghc-*/ | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" - name: Save dependencies to cache if: steps.restore-deps.outputs.cache-matched-key != format('{0}-{1}-{2}', steps.keys.outputs.deps, env.branch, steps.hash-deps.outputs.hash) uses: actions/cache/save@v4 with: path: ~/.cabal/store key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ steps.hash-deps.outputs.hash }} - name: Copy libs from container shell: bash run: | ARCH=x86_64 GHC_VER=${{ needs.variables.outputs.GHC_VER }} BUILD_DIR=$(echo dist-newstyle/build/${ARCH}-linux/ghc-${GHC_VER}/simplex-chat-*) mkdir -p postgres-libs/libs cp ${BUILD_DIR}/build/libsimplex.so postgres-libs/libs/ cp ${BUILD_DIR}/build/deps/* postgres-libs/libs/ (cd postgres-libs && zip -r simplex-chat-libs-linux-x86_64-postgres.zip libs) - name: Upload postgres library shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} TAG: ${{ needs.maybe-release.outputs.libs_tag }} run: gh release upload "$TAG" postgres-libs/*.zip --clobber --repo "${{ github.repository }}-libs" # ========================= # MacOS Build # ========================= build-macos: name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] runs-on: ${{ matrix.os }} concurrency: group: ${{ github.workflow }}-${{ github.ref }}-macos-${{ matrix.os }}-${{ needs.maybe-release.outputs.libs_tag }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} strategy: fail-fast: false matrix: include: - os: macos-latest arch: aarch64 ghc: ${{ needs.variables.outputs.GHC_VER }} cli_asset_name: simplex-chat-macos-aarch64 desktop_asset_name: simplex-desktop-macos-aarch64.dmg openssl_dir: "/opt/homebrew/opt" - os: macos-15-intel arch: x86_64 ghc: ${{ needs.variables.outputs.GHC_VER }} cli_asset_name: simplex-chat-macos-x86-64 desktop_asset_name: simplex-desktop-macos-x86_64.dmg openssl_dir: "/usr/local/opt" steps: - name: Checkout Code uses: actions/checkout@v3 with: submodules: recursive - name: Point Command Line Tools SDK to Xcode SDK shell: bash run: sudo ln -sfn "$(xcrun --sdk macosx --show-sdk-path)" /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk - name: Compute toolchain cache key id: toolchain-key if: env.release != 'true' shell: bash run: echo "key=ghcup-${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}-cabal3.10.2.0-$({ sw_vers -productVersion; clang --version; } | shasum -a 256 | cut -c1-16)" >> "$GITHUB_OUTPUT" - name: Restore toolchain from cache id: restore-toolchain if: env.release != 'true' uses: actions/cache/restore@v4 with: path: | ~/.ghcup/ghc/${{ matrix.ghc }} ~/.ghcup/bin/*-${{ matrix.ghc }} ~/.ghcup/bin/cabal-3.10.2.0 key: ${{ steps.toolchain-key.outputs.key }} - name: Warn if toolchain not in cache if: env.release != 'true' && steps.restore-toolchain.outputs.cache-hit != 'true' run: echo "::warning title=Cache miss::${{ steps.toolchain-key.outputs.key }}" - name: Prepare build id: prepare uses: ./.github/actions/prepare-build with: java_ver: ${{ needs.variables.outputs.JAVA_VER }} ghc_ver: ${{ matrix.ghc }} github_ref: ${{ github.ref }} - name: Save toolchain to cache if: env.release != 'true' && steps.restore-toolchain.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | ~/.ghcup/ghc/${{ matrix.ghc }} ~/.ghcup/bin/*-${{ matrix.ghc }} ~/.ghcup/bin/cabal-3.10.2.0 key: ${{ steps.toolchain-key.outputs.key }} - name: Install OpenSSL run: brew install openssl@3.0 - name: Prepare cabal.project.local shell: bash run: | echo "ignore-project: False" >> cabal.project.local echo "package simplexmq" >> cabal.project.local echo " extra-include-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/include" >> cabal.project.local echo " extra-lib-dirs: ${{ matrix.openssl_dir}}/openssl@3.0/lib" >> cabal.project.local echo "" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " extra-include-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/include" >> cabal.project.local echo " extra-lib-dirs: ${{ matrix.openssl_dir }}/openssl@3.0/lib" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local - name: Compute cache key prefixes id: keys if: env.release != 'true' shell: bash run: | toolchain=$({ sw_vers -productVersion; clang --version; } | shasum -a 256 | cut -c1-16) target=${{ matrix.os }}-${{ matrix.arch }}-ghc${{ matrix.ghc }}-$toolchain echo "deps=deps-$target" >> "$GITHUB_OUTPUT" echo "dist=dist-$target" >> "$GITHUB_OUTPUT" - name: Restore dependencies from cache id: restore-deps if: env.release != 'true' uses: actions/cache/restore@v4 with: path: ${{ steps.prepare.outputs.cabal-store }} key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.deps }}-${{ env.branch }}- ${{ steps.keys.outputs.deps }}- - name: Warn if dependencies not in cache if: env.release != 'true' && steps.restore-deps.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.deps }}-${{ env.branch }}" - name: Restore source repositories from cache id: restore-src if: env.release != 'true' uses: actions/cache/restore@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} restore-keys: src-${{ runner.os }}- - name: Warn if source repositories not in cache if: env.release != 'true' && steps.restore-src.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::src-${{ runner.os }}" - name: Restore build directory from cache id: restore-build if: env.release != 'true' && !needs.maybe-release.outputs.libs_tag uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-${{ env.opt }}- - name: Warn if build directory not in cache if: steps.restore-build.outcome == 'success' && steps.restore-build.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}" - name: Build CLI id: mac_cli_build shell: bash run: | scripts/ci/prune-old-builds.sh dist-newstyle cabal build -j --enable-tests ${{ env.ghc_options }} path=$(cabal list-bin simplex-chat | tail -n 1) echo "bin_path=$path" >> $GITHUB_OUTPUT echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Compute dependency cache key id: hash-deps if: env.release != 'true' shell: bash run: echo "hash=$(ls '${{ steps.prepare.outputs.cabal-store }}'/ghc-*/ | shasum -a 256 | cut -c1-16)" >> "$GITHUB_OUTPUT" - name: Save dependencies to cache if: env.release != 'true' && steps.restore-deps.outputs.cache-matched-key != format('{0}-{1}-{2}', steps.keys.outputs.deps, env.branch, steps.hash-deps.outputs.hash) uses: actions/cache/save@v4 with: path: ${{ steps.prepare.outputs.cabal-store }} key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ steps.hash-deps.outputs.hash }} - name: Save source repositories to cache if: env.release != 'true' && matrix.arch == 'x86_64' && steps.restore-src.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} - name: Save build directory to cache if: env.release != 'true' && steps.restore-build.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.mac_cli_build.outputs.bin_path }} bin_name: ${{ matrix.cli_asset_name }} bin_hash: ${{ steps.mac_cli_build.outputs.bin_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build Desktop id: mac_desktop_build if: startsWith(github.ref, 'refs/tags/v') shell: bash env: APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }} APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }} APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }} run: | export ASSETS_DIR='../../assets' scripts/ci/build-desktop-mac.sh path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg) echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.mac_desktop_build.outputs.package_path }} bin_name: ${{ matrix.desktop_asset_name }} bin_hash: ${{ steps.mac_desktop_build.outputs.package_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Mount RAM disk for tests if: matrix.arch == 'aarch64' shell: bash run: | disk=$(hdiutil attach -nomount ram://1048576 | tr -d '[:space:]') diskutil erasevolume HFS+ tests "$disk" ln -s /Volumes/tests tests/tmp - name: Run tests if: matrix.arch == 'aarch64' timeout-minutes: 120 shell: bash run: | scripts/ci/test-watchdog.sh 45 & watchdog=$! i=1 attempts=1 ${{ (github.ref == 'refs/heads/stable' || startsWith(github.ref, 'refs/tags/v')) }} && attempts=3 while [ "$i" -le "$attempts" ]; do if cabal test --test-show-details=direct --test-options="+RTS -s -RTS" ${{ env.ghc_options }}; then break else echo "Attempt $i failed, retrying..." i=$((i + 1)) sleep 1 fi done kill "$watchdog" || true if [ "$i" -gt "$attempts" ]; then echo "All "$attempts" attempts failed." exit 1 fi - name: Build library id: mac_lib_build if: ${{ !cancelled() && needs.maybe-release.outputs.libs_tag && steps.mac_cli_build.outcome == 'success' }} shell: bash env: APPLE_SIMPLEX_SIGNING_KEYCHAIN: ${{ secrets.APPLE_SIMPLEX_SIGNING_KEYCHAIN }} APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }} APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }} run: | SKIP_VLC=1 scripts/desktop/build-lib-mac.sh name=simplex-chat-libs-macos-${{ matrix.arch }} mkdir -p release-libs/$name/libs find apps/multiplatform/common/src/commonMain/cpp/desktop/libs/mac-${{ matrix.arch }} -maxdepth 1 -type f -exec cp {} release-libs/$name/libs/ \; scripts/ci/sign-libs-mac.sh release-libs/$name/libs (cd release-libs/$name && zip -r ../$name.zip libs) - name: Upload library if: ${{ !cancelled() && steps.mac_lib_build.outcome == 'success' }} shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} TAG: ${{ needs.maybe-release.outputs.libs_tag }} run: gh release upload "$TAG" release-libs/*.zip --clobber --repo "${{ github.repository }}-libs" - name: Setup Node.js id: setup-node if: ${{ !cancelled() && (github.event_name == 'pull_request' || needs.maybe-release.outputs.addon_draft || needs.maybe-release.outputs.publish) }} uses: actions/setup-node@v7 with: node-version: 22 - name: Setup Python id: setup-python if: ${{ !cancelled() && steps.setup-node.outcome == 'success' }} uses: actions/setup-python@v7 with: python-version: "3.12" - name: Package tests (pull request) if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && github.event_name == 'pull_request' }} timeout-minutes: 30 shell: bash run: | scripts/ci/package-tests.sh build macos-${{ matrix.arch }} scripts/ci/package-tests.sh test - name: Add-on if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.addon_draft }} timeout-minutes: 30 shell: bash env: LIBS_RELEASE: ${{ needs.maybe-release.outputs.libs_latest }} ADDON_TAG: ${{ needs.maybe-release.outputs.addon_tag }} LIBS_REPOSITORY: ${{ github.repository }}-libs GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} run: scripts/ci/package-tests.sh addon macos-${{ matrix.arch }} - name: Package tests id: package-tests if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.publish }} timeout-minutes: 30 shell: bash run: | scripts/ci/package-tests.sh publish macos-${{ matrix.arch }} ${{ needs.maybe-release.outputs.publish_nodejs && 'nodejs' || '' }} ${{ needs.maybe-release.outputs.publish_python && 'python' || '' }} echo "macos-${{ matrix.arch }}" > "$RUNNER_TEMP/package-tests" - name: Package tests result if: ${{ !cancelled() && steps.package-tests.outcome == 'success' }} uses: actions/upload-artifact@v7 with: name: package-tests-macos-${{ matrix.arch }} path: ${{ runner.temp }}/package-tests - name: Remove -O2 build directory id: remove-o2 if: env.cache_o0 == 'true' && !cancelled() && steps.mac_cli_build.outcome == 'success' shell: bash run: find dist-newstyle -mindepth 1 -maxdepth 1 ! -name src -exec rm -rf {} + - name: Restore -O0 build directory from cache id: restore-build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-O0- - name: Build at -O0 for pull request caches id: build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} shell: bash run: | scripts/ci/prune-old-builds.sh dist-newstyle cabal build -j --enable-tests --ghc-options=-O0 - name: Save -O0 build directory to cache if: ${{ !cancelled() && steps.build-o0.outcome == 'success' && steps.restore-build-o0.outputs.cache-hit != 'true' }} uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} # ========================= # Windows Build # ========================= build-windows: name: "${{ matrix.os }} (CLI,Desktop), GHC: ${{ matrix.ghc }}" needs: [maybe-release, variables] runs-on: ${{ matrix.os }} concurrency: group: ${{ github.workflow }}-${{ github.ref }}-windows-${{ matrix.os }}-${{ needs.maybe-release.outputs.libs_tag }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} strategy: fail-fast: false matrix: include: - os: windows-latest ghc: ${{ needs.variables.outputs.GHC_VER }} cli_asset_name: simplex-chat-windows-x86-64 desktop_asset_name: simplex-desktop-windows-x86_64.msi steps: - name: Checkout Code uses: actions/checkout@v3 with: submodules: recursive - name: Prepare build id: prepare uses: ./.github/actions/prepare-build with: java_ver: ${{ needs.variables.outputs.JAVA_VER }} ghc_ver: ${{ matrix.ghc }} github_ref: ${{ github.ref }} - name: Configure pagefile (Windows) uses: simplex-chat/configure-pagefile-action@v1.4 with: minimum-size: 16GB maximum-size: 16GB disk-root: "C:" - name: 'Setup MSYS2' uses: simplex-chat/setup-msys2@v2 with: msystem: ucrt64 update: true install: >- git perl make zip pacboy: >- toolchain:p cmake:p - name: Compute cache key prefixes id: keys if: env.release != 'true' shell: msys2 {0} run: | toolchain=$(gcc --version | sha256sum | cut -c1-16) target=windows-x86_64-ghc${{ matrix.ghc }}-$toolchain echo "deps=deps-$target" >> "$GITHUB_OUTPUT" echo "dist=dist-$target" >> "$GITHUB_OUTPUT" - name: Restore dependencies from cache id: restore-deps if: env.release != 'true' uses: actions/cache/restore@v4 with: path: ${{ steps.prepare.outputs.cabal-store }} key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.deps }}-${{ env.branch }}- ${{ steps.keys.outputs.deps }}- - name: Warn if dependencies not in cache if: env.release != 'true' && steps.restore-deps.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.deps }}-${{ env.branch }}" - name: Restore source repositories from cache id: restore-src if: env.release != 'true' uses: actions/cache/restore@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} restore-keys: src-${{ runner.os }}- - name: Warn if source repositories not in cache if: env.release != 'true' && steps.restore-src.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::src-${{ runner.os }}" - name: Restore build directory from cache id: restore-build if: env.release != 'true' && !needs.maybe-release.outputs.libs_tag uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-${{ env.opt }}- - name: Warn if build directory not in cache if: steps.restore-build.outcome == 'success' && steps.restore-build.outputs.cache-matched-key == '' run: echo "::warning title=Cache miss::${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}" # rm -rf dist-newstyle/src/{direct-sq,simplexmq}* is here because of the bug in cabal's dependency which prevents second build from finishing # (simplexmq is removed because cabal cannot delete its read-only git submodule pack files - blst, libbbs - on Windows) - name: Build CLI id: windows_cli_build shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/prepare-openssl-windows.sh openssl_windows_style_path=$(echo `pwd`/dist-newstyle/openssl-3.0.15 | sed 's#/\([a-zA-Z]\)#\1:#' | sed 's#/#\\#g') rm cabal.project.local 2>/dev/null || true echo "ignore-project: False" >> cabal.project.local echo "package direct-sqlcipher" >> cabal.project.local echo " flags: +openssl" >> cabal.project.local echo " extra-include-dirs: $openssl_windows_style_path\include" >> cabal.project.local echo " extra-lib-dirs: $openssl_windows_style_path" >> cabal.project.local scripts/ci/prune-old-builds.sh dist-newstyle rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* sed -i "s/, unix /--, unix /" simplex-chat.cabal cabal build -j --enable-tests ${{ env.ghc_options }} rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* path=$(cabal list-bin simplex-chat | tail -n 1) echo "bin_path=$path" >> $GITHUB_OUTPUT echo "bin_hash=$(echo SHA2-256\(${{ matrix.cli_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Compute dependency cache key id: hash-deps if: env.release != 'true' shell: msys2 {0} run: echo "hash=$(ls "$(cygpath -u '${{ steps.prepare.outputs.cabal-store }}')"/ghc-*/ | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" - name: Save dependencies to cache if: env.release != 'true' && steps.restore-deps.outputs.cache-matched-key != format('{0}-{1}-{2}', steps.keys.outputs.deps, env.branch, steps.hash-deps.outputs.hash) uses: actions/cache/save@v4 with: path: ${{ steps.prepare.outputs.cabal-store }} key: ${{ steps.keys.outputs.deps }}-${{ env.branch }}-${{ steps.hash-deps.outputs.hash }} - name: Save source repositories to cache if: env.release != 'true' && steps.restore-src.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: dist-newstyle/src key: src-${{ runner.os }}-${{ hashFiles('cabal.project') }} - name: Save build directory to cache if: env.release != 'true' && steps.restore-build.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-${{ env.opt }}-${{ env.branch }}-${{ github.sha }} - name: Build library if: needs.maybe-release.outputs.libs_tag shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) SKIP_VLC=1 scripts/desktop/build-lib-windows.sh dlltool -d libsimplex.dll.def -l libsimplex.lib -D libsimplex.dll name=simplex-chat-libs-windows-x86_64 mkdir -p release-libs/$name/libs find apps/multiplatform/common/src/commonMain/cpp/desktop/libs/windows-x86_64 -maxdepth 1 -type f -exec cp {} release-libs/$name/libs/ \; cp libsimplex.lib release-libs/$name/libs/ (cd release-libs/$name && zip -r ../$name.zip libs) - name: Upload library if: needs.maybe-release.outputs.libs_tag shell: bash env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} TAG: ${{ needs.maybe-release.outputs.libs_tag }} run: gh release upload "$TAG" release-libs/*.zip --clobber --repo "${{ github.repository }}-libs" - name: Setup Node.js id: setup-node if: ${{ !cancelled() && (github.event_name == 'pull_request' || needs.maybe-release.outputs.addon_draft || needs.maybe-release.outputs.publish) }} uses: actions/setup-node@v7 with: node-version: 22 - name: Setup Python id: setup-python if: ${{ !cancelled() && steps.setup-node.outcome == 'success' }} uses: actions/setup-python@v7 with: python-version: "3.12" - name: Package tests (pull request) if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && github.event_name == 'pull_request' }} timeout-minutes: 30 shell: bash run: | scripts/ci/package-tests.sh build windows-x86_64 scripts/ci/package-tests.sh test - name: Add-on if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.addon_draft }} timeout-minutes: 30 shell: bash env: LIBS_RELEASE: ${{ needs.maybe-release.outputs.libs_latest }} ADDON_TAG: ${{ needs.maybe-release.outputs.addon_tag }} LIBS_REPOSITORY: ${{ github.repository }}-libs GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} run: scripts/ci/package-tests.sh addon windows-x86_64 - name: Package tests id: package-tests if: ${{ !cancelled() && steps.setup-python.outcome == 'success' && needs.maybe-release.outputs.publish }} timeout-minutes: 30 shell: bash run: | scripts/ci/package-tests.sh publish windows-x86_64 ${{ needs.maybe-release.outputs.publish_nodejs && 'nodejs' || '' }} ${{ needs.maybe-release.outputs.publish_python && 'python' || '' }} echo "windows-x86_64" > "$RUNNER_TEMP/package-tests" - name: Package tests result if: ${{ !cancelled() && steps.package-tests.outcome == 'success' }} uses: actions/upload-artifact@v7 with: name: package-tests-windows-x86_64 path: ${{ runner.temp }}/package-tests - name: Upload CLI if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.windows_cli_build.outputs.bin_path }} bin_name: ${{ matrix.cli_asset_name }} bin_hash: ${{ steps.windows_cli_build.outputs.bin_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Build Desktop id: windows_desktop_build if: startsWith(github.ref, 'refs/tags/v') shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/build-lib-windows.sh cd apps/multiplatform ./gradlew -Psimplex.assets.dir=../../assets packageMsi rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT echo "package_hash=$(echo SHA2-256\(${{ matrix.desktop_asset_name }}\)= $(openssl sha256 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT - name: Upload Desktop if: startsWith(github.ref, 'refs/tags/v') uses: ./.github/actions/prepare-release with: bin_path: ${{ steps.windows_desktop_build.outputs.package_path }} bin_name: ${{ matrix.desktop_asset_name }} bin_hash: ${{ steps.windows_desktop_build.outputs.package_hash }} github_ref: ${{ github.ref }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Remove -O2 build directory id: remove-o2 if: env.cache_o0 == 'true' && !needs.maybe-release.outputs.libs_tag && !cancelled() && steps.windows_cli_build.outcome == 'success' shell: msys2 {0} run: find dist-newstyle -mindepth 1 -maxdepth 1 ! -name src -exec rm -rf {} + - name: Restore -O0 build directory from cache id: restore-build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} uses: actions/cache/restore@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} restore-keys: | ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}- ${{ steps.keys.outputs.dist }}-O0- - name: Build at -O0 for pull request caches id: build-o0 if: ${{ !cancelled() && steps.remove-o2.outcome == 'success' }} shell: msys2 {0} run: | export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/prepare-openssl-windows.sh scripts/ci/prune-old-builds.sh dist-newstyle rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* cabal build -j --enable-tests --ghc-options=-O0 rm -rf dist-newstyle/src/direct-sq* dist-newstyle/src/simplexmq* - name: Save -O0 build directory to cache if: ${{ !cancelled() && steps.build-o0.outcome == 'success' && steps.restore-build-o0.outputs.cache-hit != 'true' }} uses: actions/cache/save@v4 with: path: | dist-newstyle/* !dist-newstyle/src key: ${{ steps.keys.outputs.dist }}-O0-${{ env.branch }}-${{ github.sha }} # ========================= # Releases and packages # ========================= releases: needs: [maybe-release, build-linux, build-linux-push, build-linux-postgres, build-macos, build-windows] if: ${{ !cancelled() && (needs.maybe-release.outputs.libs_tag || needs.maybe-release.outputs.addon_draft) }} runs-on: ubuntu-latest env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} LIBS_REPOSITORY: ${{ github.repository }}-libs steps: - name: Checkout Code uses: actions/checkout@v6 - name: Publish library release if: needs.maybe-release.outputs.libs_tag run: python3 scripts/ci/packages_release.py publish-release "${{ needs.maybe-release.outputs.libs_tag }}" - name: Publish add-on release if: ${{ !cancelled() && needs.maybe-release.outputs.addon_draft }} run: python3 scripts/ci/packages_release.py publish-release "${{ needs.maybe-release.outputs.addon_tag }}" # Publishing uses trusted publishing (OIDC) for this workflow on npm and PyPI, # or the NPM_TOKEN and PYPI_API_TOKEN secrets when they are set. # Test repositories: variables NPM_SCOPE, PYPI_PROJECT and PYPI_URL / PYPI_UPLOAD_URL (TestPyPI) # publish under other names. publish: needs: [maybe-release, build-linux, build-linux-push, build-macos, build-windows, releases] if: ${{ !cancelled() && (needs.maybe-release.outputs.publish || needs.maybe-release.outputs.publish_types) }} runs-on: ubuntu-latest permissions: contents: read id-token: write # set by a publish step after it succeeds, so a failed publish still reports the earlier ones outputs: types: ${{ steps.types.outputs.version }} nodejs: ${{ steps.nodejs.outputs.version }} python: ${{ steps.python.outputs.version }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_SCOPE: ${{ vars.NPM_SCOPE }} PYPI_PROJECT: ${{ vars.PYPI_PROJECT }} PYPI_URL: ${{ vars.PYPI_URL }} steps: - name: Checkout Code uses: actions/checkout@v6 - name: Download package tests results if: needs.maybe-release.outputs.publish uses: actions/download-artifact@v8 with: pattern: package-tests-* path: ${{ runner.temp }}/package-tests - name: Check package tests and pins if: needs.maybe-release.outputs.publish env: GH_TOKEN: ${{ github.token }} LIBS_REPOSITORY: ${{ github.repository }}-libs run: | passed=$(find "$RUNNER_TEMP/package-tests" -mindepth 1 -maxdepth 1 -type d -name 'package-tests-*' | wc -l) if [ "$passed" -ne 5 ]; then echo "::error::package tests passed on $passed of 5 platforms" exit 1 fi python3 scripts/ci/packages_release.py check-pins - name: Versions to publish id: plan env: GH_TOKEN: ${{ github.token }} LIBS_REPOSITORY: ${{ github.repository }}-libs run: python3 scripts/ci/packages_release.py publish-plan >> "$GITHUB_OUTPUT" - name: Setup Node.js if: steps.plan.outputs.types_version || steps.plan.outputs.nodejs_version uses: actions/setup-node@v7 with: node-version: 24 registry-url: https://registry.npmjs.org - name: Setup Python if: steps.plan.outputs.python_version uses: actions/setup-python@v7 with: python-version: "3.12" # Before the test names: the build installs @simplex-chat/types from this repository. - name: Build and test simplex-chat (npm) if: steps.plan.outputs.nodejs_version working-directory: packages/simplex-chat-nodejs run: | (cd ../simplex-chat-client/types/typescript && npm install --no-audit --no-fund && npx tsc) npm install --ignore-scripts --no-save --no-audit --no-fund ../simplex-chat-client/types/typescript npx tsc && cp src/simplex.* dist npx jest tests/libs.test.ts tests/api.unit.test.ts - name: Use test package names if: vars.NPM_SCOPE || vars.PYPI_PROJECT run: python3 scripts/ci/packages_release.py test-names - name: Publish @simplex-chat/types id: types if: steps.plan.outputs.types_version && needs.maybe-release.outputs.publish_types && needs.releases.result == 'success' working-directory: packages/simplex-chat-client/types/typescript env: VERSION: ${{ steps.plan.outputs.types_version }} DIST_TAG: ${{ steps.plan.outputs.types_dist_tag }} run: | npm install --no-audit --no-fund npm publish --provenance --access public --tag "$DIST_TAG" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Publish simplex-chat (npm) id: nodejs if: steps.plan.outputs.nodejs_version working-directory: packages/simplex-chat-nodejs env: VERSION: ${{ steps.plan.outputs.nodejs_version }} DIST_TAG: ${{ steps.plan.outputs.nodejs_dist_tag }} run: | npm publish --provenance --access public --tag "$DIST_TAG" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Build and test simplex-chat (PyPI) if: steps.plan.outputs.python_version working-directory: packages/simplex-chat-python run: | python -m pip install build -e ".[test]" python -m pytest -q python -m build - name: Publish simplex-chat (PyPI) if: steps.plan.outputs.python_version uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: packages/simplex-chat-python/dist repository-url: ${{ vars.PYPI_UPLOAD_URL || 'https://upload.pypi.org/legacy/' }} password: ${{ secrets.PYPI_API_TOKEN }} - name: Report the published PyPI version id: python if: steps.plan.outputs.python_version env: VERSION: ${{ steps.plan.outputs.python_version }} run: echo "version=$VERSION" >> "$GITHUB_OUTPUT" record: needs: publish if: ${{ !cancelled() && (needs.publish.outputs.types || needs.publish.outputs.nodejs || needs.publish.outputs.python) }} runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v6 - name: Record published versions env: GH_TOKEN: ${{ secrets.NODEJS_REPO_TOKEN }} LIBS_REPOSITORY: ${{ github.repository }}-libs TYPES: ${{ needs.publish.outputs.types }} NODEJS: ${{ needs.publish.outputs.nodejs }} PYTHON: ${{ needs.publish.outputs.python }} run: | python3 scripts/ci/packages_release.py record --commit "$GITHUB_SHA" \ --types "$TYPES" --nodejs "$NODEJS" --python "$PYTHON"