Merge pull request #126 from albeva/develop #73
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Pipeline gates `main` only — `develop` is the working branch where in-flight | |
| # work lives without CI cost. Build + test runs on direct pushes to `main` | |
| # (which includes the squash-merge of a `develop -> main` release PR). | |
| # PR runs are intentionally not wired up: the post-merge push covers the | |
| # same code and avoids a duplicate release attempt where one of the two | |
| # would fail the version-already-exists check. | |
| # | |
| # Releases are fully automatic on push to main: the version is read once | |
| # from fbide_version() in CMakeLists.txt; the build matrix builds and | |
| # tests both x64 and x86; if no `v<version>` tag exists yet, a single | |
| # GitHub Release is published with both arch zips attached. Existing | |
| # version on push fails the workflow. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'src/**' | |
| - 'resources/ide/**' | |
| - 'cmake/**' | |
| - 'CMakeLists.txt' | |
| - 'configured_files/**' | |
| - 'resources/packaging/**' | |
| - '.github/workflows/ci.yml' | |
| - '.github/actions/setup-wx/**' | |
| # Manual trigger lets the workflow run on any branch and pick a build | |
| # type — useful for one-off Debug repros without changing the script. | |
| # Manual runs do NOT publish releases. | |
| workflow_dispatch: | |
| inputs: | |
| build_type: | |
| description: CMake build type for FBIde + tests. | |
| required: true | |
| default: Release | |
| type: choice | |
| options: [Release, Debug, RelWithDebInfo] | |
| # Per-target gates for manual runs. Default OFF so a manual run from | |
| # develop builds nothing until you tick the target(s) you want to | |
| # test — a single arch instead of the whole matrix. Push-to-main is | |
| # unaffected: push events don't expose `inputs.*`, so the targets | |
| # step builds the full matrix regardless of these defaults. | |
| build_win64: | |
| description: Build Windows x64 | |
| required: false | |
| default: false | |
| type: boolean | |
| build_win32: | |
| description: Build Windows x86 | |
| required: false | |
| default: false | |
| type: boolean | |
| build_winarm64: | |
| description: Build Windows arm64 | |
| required: false | |
| default: false | |
| type: boolean | |
| build_linux_appimage_x86_64: | |
| description: Build Linux AppImage (x86_64) | |
| required: false | |
| default: false | |
| type: boolean | |
| build_linux_appimage_aarch64: | |
| description: Build Linux AppImage (arm64) | |
| required: false | |
| default: false | |
| type: boolean | |
| build_macos_arm64: | |
| description: Build macOS DMG (arm64) | |
| required: false | |
| default: false | |
| type: boolean | |
| build_macos_x86_64: | |
| description: Build macOS DMG (x86_64) | |
| required: false | |
| default: false | |
| type: boolean | |
| publish_release: | |
| description: Publish a GitHub Release with the built artefacts | |
| required: false | |
| default: false | |
| type: boolean | |
| # Cancel an older run on the same ref/PR when a new one starts — saves runner | |
| # minutes when commits land in quick succession. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Opt every JS-based action into Node 24 ahead of GitHub's June 2026 | |
| # default switch. Lets us stay on stable major tags (actions/checkout@v5, | |
| # ilammy/msvc-dev-cmd@v1, etc.) without waiting for upstream re-tags. | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | |
| jobs: | |
| # Resolve the configured FBIde version once per workflow run and decide | |
| # whether this run is going to publish a release. Uses the real | |
| # CMakeLists.txt with -DFBIDE_VERSION_ONLY=ON, which short-circuits | |
| # before any compiler detection or wxWidgets lookup — so this runs on | |
| # Ubuntu without MSVC. | |
| version: | |
| name: version | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.resolve.outputs.value }} | |
| tag: ${{ steps.resolve.outputs.tag }} | |
| release: ${{ steps.resolve.outputs.release }} | |
| win-targets: ${{ steps.targets.outputs.win-targets }} | |
| linux-targets: ${{ steps.targets.outputs.linux-targets }} | |
| macos-targets: ${{ steps.targets.outputs.macos-targets }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| # Need full tag list so the existing-tag check has the truth. | |
| fetch-tags: true | |
| fetch-depth: 0 | |
| # Hosted Ubuntu runner ships an older CMake; FBIde requires 4.0+. | |
| - name: Set up CMake | |
| uses: lukka/get-cmake@latest | |
| with: | |
| cmakeVersion: latest | |
| - name: Resolve version (and verify it is unreleased) | |
| id: resolve | |
| shell: bash | |
| run: | | |
| cmake -B build/version-probe -S . -DFBIDE_VERSION_ONLY=ON | |
| version="$(cat build/version-probe/version.txt)" | |
| tag="v${version}" | |
| echo "value=${version}" >> "$GITHUB_OUTPUT" | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| # Only push-to-main runs publish releases; workflow_dispatch is | |
| # build-and-test only. | |
| # Push-to-main always publishes; workflow_dispatch can opt in | |
| # via the publish_release checkbox so manual runs from any | |
| # branch can produce a tagged release when the operator wants | |
| # one. Either way, the tag-doesn't-exist guard runs first to | |
| # keep us from clobbering a published version. | |
| publish=false | |
| if [ "$GITHUB_EVENT_NAME" = "push" ] && [ "$GITHUB_REF" = "refs/heads/main" ]; then | |
| publish=true | |
| elif [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ inputs.publish_release }}" = "true" ]; then | |
| publish=true | |
| fi | |
| if [ "${publish}" = "true" ]; then | |
| if git rev-parse "refs/tags/${tag}" >/dev/null 2>&1; then | |
| echo "::error::Release ${tag} already exists. Bump fbide_version() in CMakeLists.txt before publishing." | |
| exit 1 | |
| fi | |
| echo "release=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "release=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Resolve which targets to build into a JSON arch list (consumed by | |
| # build-and-test's matrix) plus a boolean for the AppImage job. | |
| # Push events build everything; workflow_dispatch honours the | |
| # per-target checkboxes from the run dialog. | |
| - name: Resolve build targets | |
| id: targets | |
| shell: bash | |
| env: | |
| BUILD_WIN64: ${{ inputs.build_win64 }} | |
| BUILD_WIN32: ${{ inputs.build_win32 }} | |
| BUILD_WINARM64: ${{ inputs.build_winarm64 }} | |
| BUILD_LINUX_X86_64: ${{ inputs.build_linux_appimage_x86_64 }} | |
| BUILD_LINUX_AARCH64: ${{ inputs.build_linux_appimage_aarch64 }} | |
| BUILD_MACOS_ARM64: ${{ inputs.build_macos_arm64 }} | |
| BUILD_MACOS_X86_64: ${{ inputs.build_macos_x86_64 }} | |
| run: | | |
| # macos-targets is a JSON list of {arch, runner} objects fed | |
| # directly into build-macos's matrix.include. Both arches run | |
| # on macos-latest (Apple Silicon, newest hosted image with | |
| # newest Xcode). arm64 builds natively; x86_64 cross-compiles | |
| # with Apple Clang's built-in -arch x86_64 support (no | |
| # Rosetta needed for the compile, only for running the tests | |
| # — Rosetta is pre-installed on the hosted image). | |
| mac_arm='{"arch":"arm64","runner":"macos-latest"}' | |
| mac_x86='{"arch":"x86_64","runner":"macos-latest"}' | |
| # win-targets mirrors macos/linux-targets: a JSON list of | |
| # {arch, runner} objects fed into build-and-test's matrix.include. | |
| # x64/x86 are PINNED to windows-2022, NOT windows-latest: GitHub | |
| # migrated windows-latest to Windows Server 2025 + Visual Studio | |
| # 2026 (VS 18.x) over 8-15 Jun 2026, and VS 2026's toolset drops | |
| # Windows 7 targeting. We ship a Win7 SP1 floor, so we stay on the | |
| # last VS 2022 image. windows-2022 is GitHub's documented "needs | |
| # VS 2022" fallback — do not bump back to windows-latest unless the | |
| # Win7 floor is also dropped. arm64 builds natively on the | |
| # windows-11-arm hosted runner (no Win7 ARM64 exists, so the VS | |
| # version there is irrelevant; ctest runs the arm64 binaries | |
| # directly, no cross-compile/emulation). | |
| win_x64='{"arch":"x64","runner":"windows-2022"}' | |
| win_x86='{"arch":"x86","runner":"windows-2022"}' | |
| win_arm='{"arch":"arm64","runner":"windows-11-arm"}' | |
| # linux-targets mirrors macos-targets: a JSON list of {arch, runner} | |
| # objects fed into build-linux-appimage's matrix.include. Each arch | |
| # builds natively on its own hosted runner (no emulation). | |
| linux_x86='{"arch":"x86_64","runner":"ubuntu-24.04"}' | |
| linux_arm='{"arch":"aarch64","runner":"ubuntu-24.04-arm"}' | |
| if [ "$GITHUB_EVENT_NAME" = "push" ]; then | |
| echo "win-targets=[${win_x64},${win_x86},${win_arm}]" >> "$GITHUB_OUTPUT" | |
| echo "linux-targets=[${linux_x86},${linux_arm}]" >> "$GITHUB_OUTPUT" | |
| echo "macos-targets=[${mac_arm},${mac_x86}]" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| win=() | |
| [ "$BUILD_WIN64" = "true" ] && win+=("${win_x64}") | |
| [ "$BUILD_WIN32" = "true" ] && win+=("${win_x86}") | |
| [ "$BUILD_WINARM64" = "true" ] && win+=("${win_arm}") | |
| IFS=, | |
| echo "win-targets=[${win[*]}]" >> "$GITHUB_OUTPUT" | |
| linux=() | |
| [ "${BUILD_LINUX_X86_64:-true}" = "true" ] && linux+=("${linux_x86}") | |
| [ "${BUILD_LINUX_AARCH64:-true}" = "true" ] && linux+=("${linux_arm}") | |
| IFS=, | |
| echo "linux-targets=[${linux[*]}]" >> "$GITHUB_OUTPUT" | |
| mac=() | |
| [ "${BUILD_MACOS_ARM64:-true}" = "true" ] && mac+=("${mac_arm}") | |
| [ "${BUILD_MACOS_X86_64:-true}" = "true" ] && mac+=("${mac_x86}") | |
| IFS=, | |
| echo "macos-targets=[${mac[*]}]" >> "$GITHUB_OUTPUT" | |
| build-and-test: | |
| name: build-and-test (${{ matrix.arch }}) | |
| needs: version | |
| if: needs.version.outputs.win-targets != '[]' | |
| runs-on: ${{ matrix.runner }} | |
| # 45 not 30: the x86/x64 installer step downloads + solid-compresses the | |
| # bundled FreeBASIC compiler on top of the build/test. | |
| timeout-minutes: 45 | |
| strategy: | |
| # Run every arch to completion even if one fails — easier to see | |
| # which platform broke. | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.version.outputs.win-targets) }} | |
| permissions: | |
| contents: read | |
| # Manual runs honour the chosen build type; push-to-main defaults to | |
| # Release (matches what the wxWidgets dist is built for). | |
| env: | |
| BUILD_TYPE: ${{ inputs.build_type || 'Release' }} | |
| # FreeBASIC version bundled into the x86/x64 installer. Single source for | |
| # the download (fetch-fbc), the cache key, and the wizard copy. Bump here | |
| # (and the gcc suffix in fetch-fbc.ps1's x64 URL) to move to a newer fbc. | |
| FBC_VERSION: '1.10.1' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up MSVC dev environment (${{ matrix.arch }}) | |
| uses: ilammy/[email protected] | |
| with: | |
| arch: ${{ matrix.arch }} | |
| # Hosted runners ship with CMake 3.31, but FBIde requires 4.0+ | |
| # (CMakeLists.txt:1 cmake_minimum_required). lukka/get-cmake also | |
| # provides Ninja, so a separate gha-setup-ninja step isn't needed | |
| # (gha-setup-ninja is archived as of May 2025). Track @latest on | |
| # both the action and the CMake release — pin only if a future | |
| # CMake release breaks the build. | |
| - name: Set up CMake + Ninja | |
| uses: lukka/get-cmake@latest | |
| with: | |
| cmakeVersion: latest | |
| - name: Set up wxWidgets | |
| id: wx | |
| uses: ./.github/actions/setup-wx | |
| with: | |
| wx-ref: v3.3.2 | |
| build-type: ${{ env.BUILD_TYPE }} | |
| arch: ${{ matrix.arch }} | |
| # WXWIN switches cmake/wxwidgets.cmake to CONFIG mode, which finds | |
| # wxWidgetsConfig.cmake under <dist>/lib/cmake/wxWidgets/. The | |
| # alternate path (wxWidgets_ROOT_DIR + legacy FindwxWidgets) expects | |
| # the prebuilt-binaries layout we don't ship from `cmake --install`. | |
| # | |
| # CMAKE_VS_PLATFORM_NAME (arm64 only): wxWidgetsConfig.cmake picks its | |
| # arch-specific targets subdir (vc_arm64_lib) from the generator | |
| # platform. Under Ninja that's unset, so it falls back to pointer size | |
| # — which can't tell arm64 from x64 (both 64-bit) and guesses vc_x64_lib, | |
| # missing the install. wx still reads CMAKE_VS_PLATFORM_NAME, so feeding | |
| # it the arch restores the right subdir. x64/x86 already resolve | |
| # correctly via the pointer-size fallback, so they pass nothing. | |
| - name: Configure | |
| run: > | |
| cmake -G Ninja -B build/ci | |
| -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} | |
| -DBUILD_TESTING=ON | |
| ${{ matrix.arch == 'arm64' && '-DCMAKE_VS_PLATFORM_NAME=arm64' || '' }} | |
| -DWXWIN=${{ steps.wx.outputs.wx-root }} | |
| # Build everything (fbide.exe + tests). The release packaging step | |
| # below reuses fbide.exe from this build — the small extra link is | |
| # paid for by avoiding a second configure / build pass on push. | |
| - name: Build | |
| run: cmake --build build/ci | |
| # GUI tests carry the `gui` label and are Windows-targeted, so x64/x86 | |
| # run the full suite. The windows-11-arm hosted runner has no | |
| # interactive desktop session to drive them, so skip the label there — | |
| # same treatment the Linux/macOS jobs give their GUI suites. | |
| - name: Test | |
| working-directory: build/ci | |
| run: ctest --output-on-failure ${{ matrix.arch == 'arm64' && '-LE gui' || '' }} | |
| # ---------- Release artefact (push to main, all archs) ---------- | |
| # Each matrix instance produces its own zip; the release job | |
| # downloads and attaches both to a single GitHub Release. | |
| - name: Stage install payload | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| run: cmake --install build/ci --prefix package | |
| # The READONLY sentinel marks a bundled ide/ directory as immutable, so | |
| # FBIde mirrors it to the user data dir on launch. Portable Windows zips | |
| # run in-place from a writable folder and edit ide/ directly — strip the | |
| # sentinel here. It belongs only in the AppImage distribution. | |
| - name: Strip READONLY sentinel from Windows payload | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| shell: pwsh | |
| run: Remove-Item -Force -Path package/ide/READONLY | |
| - name: Zip release artefact | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.version.outputs.version }}" | |
| $arch = @{ 'x64' = '64'; 'x86' = '32'; 'arm64' = 'arm64' }['${{ matrix.arch }}'] | |
| $zip = "fbide-$version-win$arch.zip" | |
| Compress-Archive -Path package\* -DestinationPath $zip -Force | |
| "RELEASE_ZIP=$zip" | Out-File -Append $env:GITHUB_ENV | |
| - name: Upload build artefact | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-${{ matrix.arch }} | |
| path: ${{ env.RELEASE_ZIP }} | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # ---------- Windows installer (x86/x64 only) ---------- | |
| # ARM64 ships as a portable zip only. The installer bundles the | |
| # FreeBASIC compiler and (via build-installer.ps1's own staging) keeps | |
| # the READONLY sentinel in ide\ so an installed copy mirrors resources | |
| # to the user data dir — unlike the relocatable zip above, which strips | |
| # it. build-installer.ps1 re-stages independently, so it is unaffected | |
| # by the READONLY strip done for the zip. | |
| - name: Cache FreeBASIC compiler | |
| if: matrix.arch != 'arm64' && (needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch') | |
| uses: actions/cache@v4 | |
| with: | |
| path: fbc-download | |
| key: fbc-${{ matrix.arch }}-${{ env.FBC_VERSION }} | |
| - name: Build Windows installer | |
| if: matrix.arch != 'arm64' && (needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch') | |
| shell: pwsh | |
| run: | | |
| choco install innosetup --no-progress -y | |
| resources/packaging/windows/fetch-fbc.ps1 -Arch ${{ matrix.arch }} -Version ${{ env.FBC_VERSION }} -DestDir fbc-stage -CacheDir fbc-download | |
| resources/packaging/windows/build-installer.ps1 ` | |
| -BuildDir build/ci -Arch ${{ matrix.arch }} ` | |
| -Version ${{ needs.version.outputs.version }} ` | |
| -FbcDir fbc-stage -FbcVersion ${{ env.FBC_VERSION }} -OutputDir installer-out | |
| $bits = if ('${{ matrix.arch }}' -eq 'x64') { '64' } else { '32' } | |
| $setup = "installer-out/fbide-${{ needs.version.outputs.version }}-win$bits-setup.exe" | |
| if (-not (Test-Path $setup)) { throw "Installer not found: $setup" } | |
| "RELEASE_SETUP=$setup" | Out-File -Append $env:GITHUB_ENV | |
| - name: Upload installer artefact | |
| if: matrix.arch != 'arm64' && (needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch') | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-installer-${{ matrix.arch }} | |
| path: ${{ env.RELEASE_SETUP }} | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # ---------------------------------------------------------------------------- | |
| # Linux AppImage. Release-only — gated on the same `version.release` flag as | |
| # the Windows zips. Builds wxGTK3 via the shared setup-wx action (Linux | |
| # branch), compiles FBIde with -DBUILD_APPIMAGE=ON so the install layout | |
| # follows FHS, then assembles an AppDir and runs linuxdeploy to produce a | |
| # self-contained AppImage. One AppImage per arch, each built natively on the | |
| # matching Ubuntu LTS runner (x86_64 + arm64 hosted runners — no emulation); | |
| # the runner's glibc sets the lower-bound for systems the AppImage runs on. | |
| # ---------------------------------------------------------------------------- | |
| build-linux-appimage: | |
| name: build-linux-appimage (${{ matrix.arch }}) | |
| needs: version | |
| if: needs.version.outputs.linux-targets != '[]' | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.version.outputs.linux-targets) }} | |
| env: | |
| BUILD_TYPE: ${{ inputs.build_type || 'Release' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up CMake + Ninja | |
| uses: lukka/get-cmake@latest | |
| with: | |
| cmakeVersion: latest | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| # libcurl4-openssl-dev: links the libcurl backend that static wx's | |
| # wxWebRequest pulls in (and bundles libcurl into the AppImage). | |
| sudo apt-get install -y --no-install-recommends \ | |
| build-essential pkg-config \ | |
| libgtk-3-dev libglib2.0-dev libsecret-1-dev libnotify-dev \ | |
| libcurl4-openssl-dev \ | |
| imagemagick file fuse libfuse2 | |
| - name: Set up wxWidgets (GTK3) | |
| id: wx | |
| uses: ./.github/actions/setup-wx | |
| with: | |
| wx-ref: v3.3.2 | |
| build-type: ${{ env.BUILD_TYPE }} | |
| # Partition the wx cache by arch — the x86_64 and aarch64 AppImage | |
| # jobs run on separate runners and must not share a cache, or one | |
| # restores the other's wx dist (with the wrong /usr/lib/<triplet> | |
| # absolute lib paths baked into its CMake link interface). | |
| arch: ${{ matrix.arch }} | |
| # WXWIN switches cmake/wxwidgets.cmake to CONFIG mode (matches the | |
| # Windows path) and finds wxWidgetsConfig.cmake under the cached | |
| # dist's lib/cmake/wxWidgets/. wxWidgets_ROOT_DIR + the legacy | |
| # FindwxWidgets module expects the prebuilt-binaries layout we don't | |
| # produce from `cmake --install`. | |
| - name: Configure | |
| run: > | |
| cmake -G Ninja -B build/ci | |
| -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} | |
| -DBUILD_TESTING=ON | |
| -DBUILD_APPIMAGE=ON | |
| -DWXWIN=${{ steps.wx.outputs.wx-root }} | |
| - name: Build | |
| run: cmake --build build/ci | |
| # CTest exists for parity with the Windows job; skip GUI tests because | |
| # the runner has no display (xvfb would mask real failures). GUI suites | |
| # carry the `gui` label (see tests/CMakeLists.txt). | |
| - name: Test (non-GUI) | |
| working-directory: build/ci | |
| run: ctest --output-on-failure -LE gui | |
| - name: Stage AppDir | |
| run: | | |
| set -eux | |
| APPDIR="${GITHUB_WORKSPACE}/AppDir" | |
| rm -rf "${APPDIR}" | |
| cmake --install build/ci --prefix "${APPDIR}/usr" | |
| # App + document icons, .desktop, MIME definitions and metainfo. | |
| resources/packaging/linux/stage-appdir.sh "${APPDIR}" "${GITHUB_WORKSPACE}" | |
| - name: Download linuxdeploy | |
| run: | | |
| set -eux | |
| curl -fL -o linuxdeploy \ | |
| https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${{ matrix.arch }}.AppImage | |
| curl -fL -o linuxdeploy-plugin-gtk \ | |
| https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh | |
| chmod +x linuxdeploy linuxdeploy-plugin-gtk | |
| - name: Build AppImage | |
| env: | |
| LDAI_OUTPUT: fbide-${{ needs.version.outputs.version }}-${{ matrix.arch }}.AppImage | |
| # Pin the target arch so linuxdeploy's appimage plugin picks the | |
| # matching runtime instead of relying on auto-detection. | |
| ARCH: ${{ matrix.arch }} | |
| run: | | |
| set -eux | |
| export PATH="${GITHUB_WORKSPACE}:${PATH}" | |
| # Pass the 256px icon explicitly so linuxdeploy makes .DirIcon (the | |
| # AppImage's own icon, used by thumbnailers / AppImageLauncher) crisp. | |
| # Auto-discovery otherwise picks a small hicolor size (64px). | |
| ./linuxdeploy --appdir AppDir \ | |
| --icon-file resources/images/fbide/256.png --icon-filename fbide \ | |
| --plugin gtk --output appimage | |
| test -f "${LDAI_OUTPUT}" | |
| echo "RELEASE_APPIMAGE=${LDAI_OUTPUT}" >> "$GITHUB_ENV" | |
| - name: Upload build artefact | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-linux-appimage-${{ matrix.arch }} | |
| path: ${{ env.RELEASE_APPIMAGE }} | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # ---------------------------------------------------------------------------- | |
| # macOS DMG. One per arch, both built on the same macos-latest runner | |
| # (Apple Silicon, newest Xcode the hosted image carries). Uses the | |
| # default Apple Clang — it cross-compiles to x86_64 natively (-arch | |
| # x86_64) and links against Apple's universal libc++ in | |
| # /usr/lib/libc++.dylib, which is on every Mac and already covers | |
| # the C++23 stdlib FBIde uses (std::ranges::contains et al.). No | |
| # Homebrew, no dylib bundling, no fat-binary lipo. | |
| # ---------------------------------------------------------------------------- | |
| build-macos: | |
| name: build-macos (${{ matrix.arch }}) | |
| needs: version | |
| if: needs.version.outputs.macos-targets != '[]' | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.version.outputs.macos-targets) }} | |
| env: | |
| BUILD_TYPE: ${{ inputs.build_type || 'Release' }} | |
| # Single source of truth for the macOS floor — passed to both wx | |
| # (via setup-wx) and to FBIde's configure. Keep aligned with | |
| # LSMinimumSystemVersion in configured_files/Info.plist.in. | |
| MACOSX_DEPLOYMENT_TARGET: '11.0' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| # Hosted macos-latest runners ship a recent CMake, but FBIde | |
| # requires CMake 4.0+; lukka/get-cmake also provides Ninja. | |
| - name: Set up CMake + Ninja | |
| uses: lukka/get-cmake@latest | |
| with: | |
| cmakeVersion: latest | |
| # Rosetta is needed only for *running* the x86_64 test binaries | |
| # on this arm64 host — the compile path is native arm64 clang | |
| # producing x86_64 code. `--install-rosetta` is idempotent. | |
| - name: Enable Rosetta (for x86_64 ctest under arm64 host) | |
| if: matrix.arch == 'x86_64' | |
| run: sudo softwareupdate --install-rosetta --agree-to-license | |
| - name: Set up wxWidgets (osx_cocoa, ${{ matrix.arch }}) | |
| id: wx | |
| uses: ./.github/actions/setup-wx | |
| with: | |
| wx-ref: v3.3.2 | |
| build-type: ${{ env.BUILD_TYPE }} | |
| arch: ${{ matrix.arch }} | |
| # The matrix runs this twice (once per arch) on the same runner. | |
| # Apple Clang's `-arch` support handles both cross- and native- | |
| # compilation in one toolchain; the wx build above used the | |
| # same Apple Clang via setup-wx, so ABIs line up at link time. | |
| - name: Configure | |
| run: > | |
| cmake -G Ninja -B build/ci | |
| -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} | |
| -DBUILD_TESTING=ON | |
| -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} | |
| -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 | |
| -DWXWIN=${{ steps.wx.outputs.wx-root }} | |
| - name: Build | |
| run: cmake --build build/ci | |
| # GUI tests carry the `gui` label (see tests/CMakeLists.txt) and | |
| # are Windows-targeted; skip them here just like the Linux job. | |
| # For x86_64 the test binaries run under Rosetta on this arm64 | |
| # host (enabled above); arm64 tests run native. | |
| - name: Test (non-GUI) | |
| working-directory: build/ci | |
| run: ctest --output-on-failure -LE gui | |
| # Spot-check: the produced binary's arch matches the matrix and | |
| # the deployment target is the one we set. Catches a toolchain | |
| # change quietly drifting in a future runner image refresh. | |
| - name: Verify binary arch + deployment target | |
| run: | | |
| set -eux | |
| binary="bin/fbide.app/Contents/MacOS/fbide" | |
| file "$binary" | |
| arches=$(lipo -archs "$binary") | |
| echo "Arches: $arches" | |
| [ "$arches" = "${{ matrix.arch }}" ] | |
| # LC_BUILD_VERSION encodes minos as e.g. "minos 11.0". | |
| otool -l "$binary" | awk '/LC_BUILD_VERSION/{f=1} f && /minos/{print; exit}' | grep -q "minos ${MACOSX_DEPLOYMENT_TARGET}" | |
| # Ad-hoc sign the bundle. We don't ship a Developer ID | |
| # signature (no paid Apple Developer Program subscription), but | |
| # the binary needs *some* signature so Gatekeeper presents the | |
| # bypassable "cannot verify developer" dialog (with the normal | |
| # System Settings → Privacy & Security → Open Anyway flow) | |
| # rather than the unbypassable "is damaged" rejection. clang's | |
| # linker normally adds an ad-hoc signature, but cross-compiled | |
| # output sometimes doesn't get it; an explicit step makes the | |
| # state deterministic. | |
| - name: Ad-hoc sign the bundle | |
| run: codesign --force --deep --sign - bin/fbide.app | |
| - name: Build DMG | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| set -eux | |
| dmg="$(resources/packaging/macos/build-dmg.sh bin/fbide.app "${VERSION}" "${{ matrix.arch }}" .)" | |
| echo "RELEASE_DMG=$(basename "${dmg}")" >> "$GITHUB_ENV" | |
| - name: Upload build artefact | |
| if: needs.version.outputs.release == 'true' || github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-macos-${{ matrix.arch }} | |
| path: ${{ env.RELEASE_DMG }} | |
| retention-days: 7 | |
| if-no-files-found: error | |
| release: | |
| name: release | |
| needs: [version, build-and-test, build-linux-appimage, build-macos] | |
| # `always()` runs the gate regardless of whether upstream jobs were | |
| # skipped (selective targets via workflow_dispatch) or not. Then we | |
| # require a publishable run AND at least one upstream success — a | |
| # full skip / failure across every build job leaves nothing to | |
| # release. | |
| if: | | |
| always() | |
| && needs.version.outputs.release == 'true' | |
| && (needs.build-and-test.result == 'success' | |
| || needs.build-linux-appimage.result == 'success' | |
| || needs.build-macos.result == 'success') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download release artefacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: artefacts | |
| pattern: release-* | |
| merge-multiple: true | |
| # softprops/action-gh-release creates the tag (on the run's commit | |
| # SHA) and the matching release in one step. Pre-release flag is | |
| # auto-derived: any version containing '-' (i.e. has an | |
| # .alpha-N / .beta-N / .rc-N suffix) is marked pre-release. | |
| # `body` is prepended to the auto-generated commit notes — used | |
| # to give download-and-install instructions per platform. | |
| - name: Publish GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: FBIde ${{ needs.version.outputs.version }} | |
| tag_name: ${{ needs.version.outputs.tag }} | |
| target_commitish: ${{ github.sha }} | |
| prerelease: ${{ contains(needs.version.outputs.version, '-') }} | |
| generate_release_notes: true | |
| body: | | |
| ## Downloads | |
| - **Windows**: | |
| - Installer (recommended, Intel/AMD): `fbide-<version>-win64-setup.exe` (64-bit) or `win32-setup.exe` (32-bit). Bundles the FreeBASIC compiler and registers file associations. | |
| - Portable: `fbide-<version>-win64.zip` (Intel/AMD 64-bit), `win32` (32-bit) or `winarm64` (ARM64). Unzip anywhere and run `fbide.exe`. ARM64 is portable-only. | |
| - **Linux**: `fbide-<version>-x86_64.AppImage` (Intel/AMD) or `fbide-<version>-aarch64.AppImage` (ARM64). `chmod +x` and run. | |
| - **macOS**: | |
| - Apple Silicon → `fbide-<version>-macos-arm64.dmg` | |
| - Intel → `fbide-<version>-macos-x86_64.dmg` | |
| - Minimum macOS: 11.0 (Big Sur). | |
| ### macOS first-run | |
| FBIde isn't signed with an Apple Developer ID, so the | |
| first time you open it macOS will warn that *"FBIde | |
| can't be opened because Apple cannot check it for | |
| malicious software"*. To allow it: | |
| 1. Drag **FBIde.app** to **Applications**. | |
| 2. Double-click **FBIde** — dismiss the warning dialog. | |
| 3. Open **System Settings → Privacy & Security**, scroll | |
| to the *Security* section, and click **Open Anyway** | |
| next to the FBIde entry. | |
| 4. Confirm in the dialog that follows. | |
| macOS remembers the decision; subsequent launches open | |
| without warnings. | |
| files: | | |
| artefacts/*.zip | |
| artefacts/*-setup.exe | |
| artefacts/*.AppImage | |
| artefacts/*.dmg |