Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cd6a5a6
Add CI to build macOS (universal arm64+x86_64), Linux, and Windows cl…
youenchene Sep 23, 2026
22ea6ca
Document real signing/notarization as a future TODO
youenchene Sep 23, 2026
2632982
Pin Windows CI build to Qt 6.10.1 to work around aqtinstall bug
youenchene Sep 23, 2026
7cac921
Replace VLA with QByteArray in AmigaInfoFile icon decoding for MSVC p…
youenchene Sep 23, 2026
00d1b1c
Make AmigaInfoFile struct packing portable to MSVC
youenchene Sep 23, 2026
e4bac74
Use MSVC-portable __FUNCSIG__ instead of __PRETTY_FUNCTION__ in AEUti…
youenchene Sep 23, 2026
f1dff8a
Exclude ash from Windows builds: it needs POSIX termios
youenchene Sep 23, 2026
05acb0e
Port ash's interactive raw-mode terminal handling to Windows
youenchene Sep 23, 2026
26768e4
Revert "Port ash's interactive raw-mode terminal handling to Windows"
youenchene Sep 23, 2026
c3b0f90
Guard unistd.h include in filedownloader.cpp for MSVC
youenchene Sep 23, 2026
f0d9b13
Guard unistd.h include in fileuploader.cpp for MSVC
youenchene Sep 23, 2026
695711d
Add experimental Amiga cross-build job using bebbo/amiga-gcc
youenchene Sep 23, 2026
4ca807d
Fix bebbo/amiga-gcc clone URL: repo moved from GitHub to Codeberg
youenchene Sep 23, 2026
821152f
Build amiga-gcc toolchain serially to avoid a libnix4 make -j race
youenchene Sep 23, 2026
516f912
Skip amiga-gcc's broken, unused libnix4.library target
youenchene Sep 23, 2026
006ea09
Build the 'all' Makefile target so ApolloExplorerTool actually builds
youenchene Sep 23, 2026
0d87b60
ci(amiga): ship ApolloExplorerSrv.info, AEKill and AEKill.info in the…
youenchene Sep 26, 2026
c7ce943
fix(scanner): don't show a bogus INVALID host on macOS
youenchene Sep 26, 2026
1c289c9
ci(amiga): upload the package folder directly so the artifact zip unp…
youenchene Sep 26, 2026
1c45749
ci: upload each package as a single archive (no zip-in-zip) and bundl…
youenchene Sep 26, 2026
5efc2eb
Merge pull request #1 from youenchene/steady-cone
youenchene Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
name: Build clients

on:
push:
branches: [main]
tags: ["v*"]
pull_request:
workflow_dispatch:

env:
QT_VERSION: "6.11.1"

jobs:
macos:
name: macOS (universal x86_64 + arm64)
runs-on: macos-14
steps:
- uses: actions/checkout@v4

- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
target: desktop
cache: true

- name: Build
run: |
qmake -recursive QMAKE_APPLE_DEVICE_ARCHS="x86_64 arm64" MACOSX_DEPLOYMENT_TARGET="12.7.6"
make -j"$(sysctl -n hw.ncpu)"

- name: Bundle app icon
run: |
mkdir -p ApolloExplorerPC/ApolloExplorer.app/Contents/Resources/
cp ApolloExplorerPC/icons/ApolloExplorer.icns ApolloExplorerPC/ApolloExplorer.app/Contents/Resources/

- name: Deploy Qt frameworks
run: macdeployqt ApolloExplorerPC/ApolloExplorer.app -verbose=1

- name: Ad-hoc code sign
# Unsigned/unnotarized: no Apple Developer ID is available in CI. Ad-hoc
# signing (identity "-") is required regardless, since arm64 refuses to
# run any unsigned Mach-O binary at all. Users must still right-click ->
# Open (or `xattr -cr`) the first time because of the quarantine flag.
#
# TODO: real signing/notarization, deferred for now. build_macos_client.sh
# does this locally with an Apple Developer Team ID (${APPLETEAMID}) via
# `codesign --sign ${APPLETEAMID}` + `macdeployqt -sign-for-notarization=`
# + `xcrun notarytool`. To wire this into CI: add the Developer ID cert
# (base64 .p12) and APPLETEAMID as repo secrets, import the cert into a
# temporary keychain, sign with it instead of "-", then notarize + staple
# before packaging.
run: |
codesign --force --deep --sign - ApolloExplorerPC/ApolloExplorer.app
codesign --force --sign - acp/acp
codesign --force --sign - ash/ash

- name: Package
run: |
mkdir -p ApolloExplorer-macOS
mv ApolloExplorerPC/ApolloExplorer.app ApolloExplorer-macOS/
mv acp/acp ApolloExplorer-macOS/
mv ash/ash ApolloExplorer-macOS/
ditto -c -k --sequesterRsrc --keepParent ApolloExplorer-macOS ApolloExplorer-macOS-universal.zip

# archive: false => the download is this zip itself, not a zip wrapping it.
- uses: actions/upload-artifact@v7
with:
path: ApolloExplorer-macOS-universal.zip
archive: false

linux:
name: Linux (x86_64)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
target: desktop
install-deps: true
cache: true

- name: Install build tools
run: sudo apt-get update && sudo apt-get install -y build-essential

- name: Build
run: |
qmake -recursive
make -j"$(nproc)"

- name: Package
run: |
mkdir -p ApolloExplorer-Linux
mv ApolloExplorerPC/ApolloExplorer ApolloExplorer-Linux/ApolloExplorer
cp ApolloExplorerPC/ApolloExplorer.desktop ApolloExplorer-Linux/ApolloExplorer.desktop
mv acp/acp ApolloExplorer-Linux/acp
mv ash/ash ApolloExplorer-Linux/ash
tar -czf ApolloExplorer-Linux-x86_64.tar.gz ApolloExplorer-Linux

# archive: false => the download is the tar.gz itself (keeps exec bits).
- uses: actions/upload-artifact@v7
with:
path: ApolloExplorer-Linux-x86_64.tar.gz
archive: false

windows:
name: Windows (x86_64)
runs-on: windows-latest
env:
# aqtinstall cannot resolve any 6.11.x version for Windows targets: Qt
# restructured its Windows repo layout starting at 6.11.0 (added an
# arm64 cross-compile variant folder), which breaks Updates.xml
# resolution upstream in aqtinstall. Pin to the last known-good release
# here instead of env.QT_VERSION until aqtinstall ships a fix.
QT_VERSION: "6.10.1"
steps:
- uses: actions/checkout@v4

- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
target: desktop
arch: win64_msvc2022_64
cache: true

- name: Set up MSVC environment
uses: ilammy/msvc-dev-cmd@v1
with:
arch: amd64

- name: Build
shell: cmd
run: |
qmake -recursive -config release
nmake

- name: Package
shell: pwsh
run: |
# ash is excluded on win32 in ApolloExplorer.pro (see comment there):
# it needs POSIX termios, which Windows doesn't have.
New-Item -ItemType Directory -Force -Path ApolloExplorer-Windows
Move-Item ApolloExplorerPC\release\ApolloExplorer.exe ApolloExplorer-Windows\ApolloExplorer.exe
Move-Item acp\release\acp.exe ApolloExplorer-Windows\acp.exe
# Bundle the Qt DLLs, plugins and MSVC runtime next to the exes; the
# .pro files' post-link windeployqt only fills release\, not this folder.
windeployqt --release --compiler-runtime ApolloExplorer-Windows\ApolloExplorer.exe
windeployqt --release --compiler-runtime ApolloExplorer-Windows\acp.exe
# Zip the folder itself so it unzips to ApolloExplorer-Windows\.
Compress-Archive -Path ApolloExplorer-Windows -DestinationPath ApolloExplorer-Windows-x86_64.zip

# archive: false => the download is this zip itself, not a zip wrapping it.
- uses: actions/upload-artifact@v7
with:
path: ApolloExplorer-Windows-x86_64.zip
archive: false

amiga:
name: Amiga server (m68k, experimental)
runs-on: ubuntu-latest
# Best-effort: this cross-toolchain isn't officially supported by upstream
# and hasn't been verified against real hardware/emulator yet. A failure
# here must not block the desktop-client release.
continue-on-error: true
steps:
- uses: actions/checkout@v4

- name: Install toolchain build dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential lhasa gettext texinfo flex bison \
libgmp-dev libmpfr-dev libmpc-dev rsync

- name: Cache amiga-gcc toolchain
id: cache-toolchain
uses: actions/cache@v4
with:
path: /opt/amiga
key: amiga-gcc-${{ runner.os }}-amiga6-ndk3.2-libamiga-v1

- name: Build m68k-amigaos-gcc toolchain (bebbo/amiga-gcc)
if: steps.cache-toolchain.outputs.cache-hit != 'true'
run: |
# bebbo/amiga-gcc migrated off GitHub; Codeberg is its current home.
git clone --depth 1 https://codeberg.org/bebbo/amiga-gcc /tmp/amiga-gcc
cd /tmp/amiga-gcc
make update
# `min` (binutils gcc gprof libnix libgcc libnix4.library) fails
# deterministically (not a -j race - reproduces serially too):
# upstream's libnix4-support generator emits a duplicate
# __vfwprintf_total_size stub, breaking export-libnix4.c. That
# target builds libnix4.library, a shared-library variant of
# libnix that Amiga/Makefile never uses (it links -noixemul,
# i.e. plain static libnix) - so build min's other pieces
# directly and skip the broken, unneeded target.
# ndk/netinclude/libamiga: NDK3.2 headers + -lamiga, as before.
make binutils gcc gprof libnix libgcc ndk netinclude libamiga NDK=3.2 PREFIX=/opt/amiga -j"$(nproc)"

- name: Bridge NDK includes to the ApolloCrossDev layout
# Amiga/Makefile (the WDrijver/ApolloCrossDev toolchain convention) expects
# headers at ndk32-include/Include_H — the raw Aminet NDK3.2 layout — while
# bebbo/amiga-gcc installs its generated, netinclude-merged headers at
# ndk-include/. Symlink the two so both conventions resolve to the same,
# complete header set without duplicating or hand-maintaining headers.
run: |
mkdir -p /opt/amiga/m68k-amigaos/ndk32-include
ln -sfn /opt/amiga/m68k-amigaos/ndk-include /opt/amiga/m68k-amigaos/ndk32-include/Include_H

- name: Build
run: |
export PATH="/opt/amiga/bin:$PATH"
# Amiga/Makefile's first (default) target is just the server binary;
# `all` is required to also build ApolloExplorerTool.
make -C Amiga PREFIX=/opt/amiga all

- name: Package
run: |
mkdir -p ApolloExplorer-Amiga
mv Amiga/ApolloExplorerSrv ApolloExplorer-Amiga/
mv Amiga/ApolloExplorerTool ApolloExplorer-Amiga/
# Workbench icons + the AEKill IconX script (runs `ApolloExplorerTool kill`),
# same set as the upstream release's "Amiga Server" folder.
cp Amiga/dist/ApolloExplorerSrv.info Amiga/dist/AEKill Amiga/dist/AEKill.info ApolloExplorer-Amiga/
# Unzips to an ApolloExplorer-Amiga/ folder.
zip -r ApolloExplorer-Amiga-m68k.zip ApolloExplorer-Amiga

# archive: false => the download is this zip itself, not a zip wrapping it.
- uses: actions/upload-artifact@v7
with:
path: ApolloExplorer-Amiga-m68k.zip
archive: false

release:
name: Attach artifacts to release
if: startsWith(github.ref, 'refs/tags/v')
needs: [macos, linux, windows, amiga]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# v8 is required to download artifacts uploaded with `archive: false`.
- uses: actions/download-artifact@v8
with:
path: artifacts
merge-multiple: true

- run: ls -laR artifacts

- uses: softprops/action-gh-release@v2
with:
files: artifacts/**/*
1 change: 1 addition & 0 deletions Amiga/dist/AEKill
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ApolloExplorerTool kill
Binary file added Amiga/dist/AEKill.info
Binary file not shown.
Binary file added Amiga/dist/ApolloExplorerSrv.info
Binary file not shown.
18 changes: 8 additions & 10 deletions AmigaIconReader/AmigaInfoFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ static inline void putByteArrayInBuffer( QByteArray &someBuffer, quint64 &someOf
static inline QByteArray BitPlanarToChunky( QByteArray bitPlanarPixels, quint32 width, quint32 height, quint8 bitsPerPixel )
{
//Set aside some memory for our chunky icon
char indexedImageData[ width * height ];
memset( indexedImageData, 0, width * height );
QByteArray indexedImageData( static_cast<qsizetype>( width * height ), 0 );

//Load in the bitplanar bitmap
quint64 pitplaneRowByteLength = ((width+15)/16)*2;
Expand All @@ -123,15 +122,15 @@ static inline QByteArray BitPlanarToChunky( QByteArray bitPlanarPixels, quint32
quint8 bit = 7-x%8;
quint8 srcPixelByte = bitPlanarPixels.constData()[ (bitPlane * bitplaneSizeInBytes ) + ( y*pitplaneRowByteLength ) + ( x/8 ) ];
srcPixelByte = qFromBigEndian( srcPixelByte );
quint8 dstPixelByte = indexedImageData[ x+(y*width) ];
quint8 dstPixelByte = static_cast<quint8>( indexedImageData[ x+(y*width) ] );
quint8 bitComparator = (1<<bit);
quint8 resultingPixelByte = (srcPixelByte & bitComparator ? (1<<bitPlane) : 0 ) | dstPixelByte;
indexedImageData[ x+(y*width) ] = resultingPixelByte;
indexedImageData[ x+(y*width) ] = static_cast<char>( resultingPixelByte );
}
}
}

return QByteArray( indexedImageData, width * height );
return indexedImageData;
}


Expand Down Expand Up @@ -560,8 +559,7 @@ qint16 AmigaInfoFile::getPriority()
QByteArray AmigaInfoFile::decodeOS2Icon(QByteArray data, quint64 &offset, quint8 depth, quint32 width, quint32 height )
{
//Set aside some memory for our chunky icon
char indexedImageData[ width * height ];
memset( indexedImageData, 0, width * height );
QByteArray indexedImageData( static_cast<qsizetype>( width * height ), 0 );

//Load in the bitplanar bitmap
quint64 pitplaneRowByteLength = ((width+15)/16)*2;
Expand All @@ -578,18 +576,18 @@ QByteArray AmigaInfoFile::decodeOS2Icon(QByteArray data, quint64 &offset, quint8
quint8 bit = 7-x%8;
quint8 srcPixelByte = bitPlanes.constData()[ (bitPlane * bitplaneSizeInBytes ) + ( y*pitplaneRowByteLength ) + ( x/8 ) ];
srcPixelByte = qFromBigEndian( srcPixelByte );
quint8 dstPixelByte = indexedImageData[ x+(y*width) ];
quint8 dstPixelByte = static_cast<quint8>( indexedImageData[ x+(y*width) ] );
quint8 bitComparator = (1<<bit);
quint8 resultingPixelByte = (srcPixelByte & bitComparator ? (1<<bitPlane) : 0 ) | dstPixelByte;
indexedImageData[ x+(y*width) ] = resultingPixelByte;
indexedImageData[ x+(y*width) ] = static_cast<char>( resultingPixelByte );
}
}
}

//Adjust the offset
offset += bitplaneSizeInBytes * depth;

return QByteArray( indexedImageData, width * height );
return indexedImageData;
}


Expand Down
Loading
Loading