Skip to content

Merge pull request #126 from albeva/develop #73

Merge pull request #126 from albeva/develop

Merge pull request #126 from albeva/develop #73

Workflow file for this run

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