forked from Fincept-Corporation/FinceptTerminal
-
Notifications
You must be signed in to change notification settings - Fork 0
1076 lines (981 loc) · 50.9 KB
/
Copy pathbuild-cpp.yml
File metadata and controls
1076 lines (981 loc) · 50.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: Build Qt Terminal - All Platforms
on:
# Manual-only — release.yml handles tag builds / installer generation.
workflow_dispatch:
env:
BUILD_TYPE: Release
QT_VERSION: "6.8.3"
# qtserialport: the qtpositioning NMEA plugin (libqtposition_nmea) links
# Qt6SerialPort; without it linuxdeploy/macdeployqt/windeployqt fail resolving
# libQt6SerialPort. The app doesn't use serial GPS — this just satisfies deploy.
QT_MODULES: "qtcharts qtwebsockets qtmultimedia qtwebengine qtwebchannel qtpositioning qtserialport"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# ---- Windows x64 ----
- os: windows-2022
arch: x64
platform: Windows
artifact: FinceptTerminal-Windows-x64
exe: FinceptTerminal.exe
qt_arch: win64_msvc2022_64
cmake_extra: ""
# ---- Windows ARM64 ----
- os: windows-2022
arch: arm64
platform: Windows
artifact: FinceptTerminal-Windows-arm64
exe: FinceptTerminal.exe
qt_arch: win64_msvc2022_arm64_cross_compiled
cmake_extra: "-A ARM64"
# ---- Linux x64 ----
# qt_arch is NOT empty: this leg used to skip install-qt-action and
# apt-install qt6-base-dev, which is Qt 6.2.4 on ubuntu-22.04 while
# CMakeLists.txt pins Qt 6.8 (FINCEPT_QT_PIN_MODE defaults to MINOR
# → find_package(Qt6 6.8 REQUIRED)). The job could never configure.
# release.yml:588-595 documents that exact trap and installs Qt via
# aqt; the fix had simply never been mirrored here.
- os: ubuntu-22.04
arch: x64
platform: Linux
artifact: FinceptTerminal-Linux-x64
exe: FinceptTerminal
qt_arch: linux_gcc_64
cmake_extra: ""
# ---- macOS ARM64 (Apple Silicon) ----
- os: macos-15
arch: arm64
platform: macOS
artifact: FinceptTerminal-macOS-arm64
exe: FinceptTerminal
qt_arch: clang_64
cmake_extra: "-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0"
# ---- macOS x64 (Intel) ----
# Cross-compiled on ARM64 runner. CMakeLists.txt auto-disables GGML_NATIVE
# and GGML_METAL when CMAKE_OSX_ARCHITECTURES != host processor.
- os: macos-15
arch: x64
platform: macOS
artifact: FinceptTerminal-macOS-x64
exe: FinceptTerminal
qt_arch: clang_64
cmake_extra: "-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0"
runs-on: ${{ matrix.os }}
name: ${{ matrix.platform }} ${{ matrix.arch }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
# ---- ccache (macOS / Linux) ----
- name: Set up ccache
if: runner.os != 'Windows'
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ matrix.platform }}-${{ matrix.arch }}-${{ env.QT_VERSION }}
max-size: 500M
update-package-index: true
# ---- System dependencies (Linux) ----
# Ubuntu 22.04's default g++ is 11.4, but CMakeLists.txt requires GCC 12.3+.
# Install gcc-12/g++-12 from the Ubuntu Toolchain PPA (pre-enabled on
# ubuntu-22.04 runners) and pin it via CC/CXX for the rest of the job.
#
# NO apt Qt packages here — ubuntu-22.04 ships Qt 6.2 and CMakeLists.txt
# pins Qt 6.8, so `qt6-base-dev` & friends made find_package(Qt6 6.8)
# fail outright. Qt comes from install-qt-action below (as it already
# did on Windows/macOS, and as release.yml does on all three platforms).
# The XCB / GL / fontconfig system libs stay: Qt's xcb platform plugin
# links against them at build time and loads them at runtime.
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo add-apt-repository universe -y
sudo apt-get update -qq
sudo apt-get install -y \
cmake ninja-build gcc-12 g++-12 \
libssl-dev \
libsecret-1-dev \
libgl1-mesa-dev libglu1-mesa-dev \
libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev \
libxcb-cursor0 libxcb-cursor-dev \
libxcb-icccm4 libxcb-icccm4-dev \
libxcb-image0 libxcb-image0-dev \
libxcb-keysyms1 libxcb-keysyms1-dev \
libxcb-randr0 \
libxcb-render-util0 libxcb-render-util0-dev \
libxcb-shape0-dev libxcb-sync-dev libxcb-xfixes0-dev \
libxcb-xinerama0 libxcb-xinerama0-dev libxcb-xkb1 \
libdbus-1-dev libfontconfig1-dev libfreetype6-dev \
pkg-config zip
echo "CC=gcc-12" >> "$GITHUB_ENV"
echo "CXX=g++-12" >> "$GITHUB_ENV"
# ---- OpenSSL + zlib dev libs (Windows) ----
# windows-2022 runners ship only the "Light" OpenSSL (no .lib files).
# vcpkg is pre-installed on every windows-2022 runner at C:\vcpkg.
# zlib is required by find_package(ZLIB) (InstrumentDecompress.cpp).
- name: Install OpenSSL + zlib (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
set -euo pipefail
# zlib is linked STATICALLY (x64-windows-static-md). See release.yml's
# "Install OpenSSL + zlib" step for the full history: v4.1.0 shipped an
# exe importing an unbundled "z.dll" because ZLIB_ROOT was built from
# VCPKG_INSTALLATION_ROOT (backslashes, e.g. C:\vcpkg), which mangled
# the path FindZLIB consumed and let the link fall through to a stray
# system zlib named z.dll. Fix: normalise slashes + static zlib + pass
# ZLIB_ROOT/ZLIB_USE_STATIC_LIBS as -D cache vars at configure.
VCPKG_ROOT="${VCPKG_INSTALLATION_ROOT:-C:/vcpkg}"
VCPKG_ROOT="${VCPKG_ROOT//\\//}" # backslashes -> forward slashes (the v4.1.0 bug)
echo "Using vcpkg at: $VCPKG_ROOT"
"$VCPKG_ROOT/vcpkg" install openssl:x64-windows zlib:x64-windows-static-md --no-print-usage
OPENSSL_DIR="$VCPKG_ROOT/installed/x64-windows"
ZLIB_DIR="$VCPKG_ROOT/installed/x64-windows-static-md"
echo "OPENSSL_ROOT_DIR=$OPENSSL_DIR" >> "$GITHUB_ENV"
echo "ZLIB_ROOT=$ZLIB_DIR" >> "$GITHUB_ENV"
echo "OpenSSL installed at: $OPENSSL_DIR ; static zlib at: $ZLIB_DIR"
ls "$OPENSSL_DIR/lib/libcrypto.lib" && echo "libcrypto.lib confirmed"
# vcpkg's static-md zlib ships the archive as "zs.lib", which stock
# CMake FindZLIB does not search for. Alias it to "zlib.lib" (a name
# FindZLIB looks for) so find_package(ZLIB) links the static archive.
if [ ! -f "$ZLIB_DIR/lib/zlib.lib" ]; then
ALT="$(ls "$ZLIB_DIR"/lib/z*.lib 2>/dev/null | head -1 || true)"
if [ -n "$ALT" ]; then cp "$ALT" "$ZLIB_DIR/lib/zlib.lib"; echo "Aliased $(basename "$ALT") -> zlib.lib"; fi
fi
if [ ! -f "$ZLIB_DIR/lib/zlib.lib" ]; then
echo "::error::static zlib archive not found in $ZLIB_DIR/lib"
ls -la "$ZLIB_DIR/lib/" 2>&1 || true
exit 1
fi
echo "static zlib confirmed: $ZLIB_DIR/lib/zlib.lib"
# ---- Qt6 target (all platforms) ----
# Linux is no longer excluded — see the matrix comment on the Linux
# entry: apt Qt 6.2 can never satisfy CMakeLists' 6.8 pin.
- name: Install Qt6
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
arch: ${{ matrix.qt_arch }}
modules: ${{ env.QT_MODULES }}
cache: true
cache-key-prefix: qt-${{ matrix.platform }}-${{ matrix.arch }}
# ---- Qt6 x64 host tools for Windows ARM64 cross-compilation ----
# moc/uic/rcc must run on the host (x64), not the target (arm64)
- name: Install Qt6 host tools (Windows ARM64 only)
if: runner.os == 'Windows' && matrix.arch == 'arm64'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
arch: win64_msvc2022_64
modules: ${{ env.QT_MODULES }}
cache: true
set-env: false
dir: "${{ runner.temp }}/qt-host"
# ---- yt-dlp (bundled for dashboard video playback) ----
- name: Install yt-dlp
shell: bash
run: pip install yt-dlp
# ---- Strip dangling Qt SQL driver plugin CMake configs (aqt workaround) ----
# aqtinstall ships Qt6Sql's MySQL/PSQL/ODBC/Mimer *DriverPlugin CMake
# target files but NOT the plugin binaries they reference, so
# find_package(Qt6 ... Sql) aborts at CONFIGURE time ("references the
# file .../libqsqlmysql.so but this file does not exist"). We only use
# SQLite. Mirrors release.yml's identically-named step in all three of
# its build jobs. MUST run before "Configure CMake".
- name: Remove dangling Qt SQL driver plugin CMake configs
shell: bash
run: |
set -euo pipefail
SQL_CMAKE="${QT_ROOT_DIR}/lib/cmake/Qt6Sql"
if [ -d "${SQL_CMAKE}" ]; then
for drv in QMYSQLDriverPlugin QPSQLDriverPlugin QODBCDriverPlugin QMimerSQLDriverPlugin; do
rm -fv "${SQL_CMAKE}/Qt6${drv}"*.cmake || true
done
else
echo "::warning::${SQL_CMAKE} not found — skipping SQL plugin config cleanup"
fi
# ---- Configure CMake ----
# CMAKE_PREFIX_PATH is required now that Qt comes from install-qt-action
# instead of apt (it was absent while this leg relied on system Qt).
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
working-directory: fincept-qt
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_PREFIX_PATH="${QT_ROOT_DIR}" \
-DOPENSSL_ROOT_DIR=/usr \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DFINCEPT_REQUIRE_BUNDLED_YTDLP=ON \
${{ matrix.cmake_extra }}
- name: Configure CMake (Windows x64 / macOS x64 / macOS arm64)
if: (runner.os == 'Windows' && matrix.arch == 'x64') || runner.os == 'macOS'
shell: bash
working-directory: fincept-qt
run: |
OPENSSL_ARG=""
if [ -n "${OPENSSL_ROOT_DIR:-}" ]; then
OPENSSL_ARG="-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}"
fi
# ZLIB_ROOT is set only on Windows (static zlib via vcpkg); on macOS it
# is empty so find_package(ZLIB) uses the system zlib as before.
ZLIB_ARG=""
if [ -n "${ZLIB_ROOT:-}" ]; then
ZLIB_ARG="-DZLIB_ROOT=${ZLIB_ROOT} -DZLIB_USE_STATIC_LIBS=ON"
fi
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_PREFIX_PATH="${QT_ROOT_DIR}" \
${OPENSSL_ARG} \
${ZLIB_ARG} \
-DFINCEPT_REQUIRE_BUNDLED_YTDLP=ON \
${{ runner.os == 'macOS' && '-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache' || '' }} \
${{ matrix.cmake_extra }}
- name: Configure CMake (Windows ARM64)
if: runner.os == 'Windows' && matrix.arch == 'arm64'
shell: bash
working-directory: fincept-qt
run: |
HOST_QT="${{ runner.temp }}/qt-host/Qt/${{ env.QT_VERSION }}/msvc2022_64"
OPENSSL_ARG=""
if [ -n "${OPENSSL_ROOT_DIR:-}" ]; then
OPENSSL_ARG="-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}"
fi
ZLIB_ARG=""
if [ -n "${ZLIB_ROOT:-}" ]; then
ZLIB_ARG="-DZLIB_ROOT=${ZLIB_ROOT} -DZLIB_USE_STATIC_LIBS=ON"
fi
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_PREFIX_PATH="${QT_ROOT_DIR}" \
-DQT_HOST_PATH="${HOST_QT}" \
-T ClangCL \
${OPENSSL_ARG} \
${ZLIB_ARG} \
-DFINCEPT_REQUIRE_BUNDLED_YTDLP=ON \
${{ matrix.cmake_extra }}
# ---- Build ----
- name: Build
shell: bash
working-directory: fincept-qt
run: cmake --build build --config ${{ env.BUILD_TYPE }} --parallel $(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 3)
# ---- Verify binary ----
- name: Verify build output
shell: bash
working-directory: fincept-qt
run: |
if [ "${{ runner.os }}" == "Windows" ]; then
BINARY="build/Release/${{ matrix.exe }}"
elif [ "${{ runner.os }}" == "macOS" ]; then
# Qt on macOS produces a .app bundle; the binary is inside it.
# Try both plain binary and .app bundle locations.
if [ -f "build/${{ matrix.exe }}" ]; then
BINARY="build/${{ matrix.exe }}"
elif [ -f "build/${{ matrix.exe }}.app/Contents/MacOS/${{ matrix.exe }}" ]; then
BINARY="build/${{ matrix.exe }}.app/Contents/MacOS/${{ matrix.exe }}"
else
BINARY="build/${{ matrix.exe }}" # will fail the check below with a clear error
fi
else
BINARY="build/${{ matrix.exe }}"
fi
if [ ! -f "$BINARY" ]; then
echo "::error::Binary not found — searched: build/${{ matrix.exe }} and build/${{ matrix.exe }}.app/Contents/MacOS/${{ matrix.exe }}"
echo "Build directory contents:"
find build -maxdepth 3 -name "${{ matrix.exe }}" 2>/dev/null || true
exit 1
fi
echo "Binary verified: $BINARY ($(du -sh "$BINARY" | cut -f1))"
# ---- Run headless self-tests ----
# Exercise each of the freshly built binary's --selftest-* checks under Qt's
# offscreen platform (no display / GPU needed). A non-zero exit fails the
# job. Runs the build-tree binary (not the packaged AppImage / .app) so it
# needs no FUSE mount or codesigning. Skipped on cross-compiled targets
# (Windows arm64, macOS x64) whose binaries can't execute on the host
# runner's architecture. timeout-minutes bounds any unexpected hang.
#
# The flag list used to be spelled out here and DRIFTED: main.cpp
# dispatches 10 --selftest-* flags, this loop listed 9, so
# --selftest-live-table ran nowhere. It now lives in
# .github/scripts/ci_app_checks.sh, which prefers `--selftest-list` from
# the binary itself and is shared with release.yml and build-pr.yml.
- name: Run self-tests (headless)
if: "!(matrix.platform == 'Windows' && matrix.arch == 'arm64') && !(matrix.platform == 'macOS' && matrix.arch == 'x64')"
shell: bash
working-directory: fincept-qt
timeout-minutes: 25
run: |
set -euo pipefail
if [ "${{ runner.os }}" == "Windows" ]; then
BINARY="build/Release/${{ matrix.exe }}"
elif [ "${{ runner.os }}" == "macOS" ] && [ ! -f "build/${{ matrix.exe }}" ]; then
BINARY="build/${{ matrix.exe }}.app/Contents/MacOS/${{ matrix.exe }}"
else
BINARY="build/${{ matrix.exe }}"
fi
export LD_LIBRARY_PATH="${QT_ROOT_DIR}/lib:${LD_LIBRARY_PATH:-}"
bash ../.github/scripts/ci_app_checks.sh selftests "$BINARY" ci
# ---- Package: Windows (windeployqt + manual DLL sweep) ----
- name: Package (Windows)
if: runner.os == 'Windows'
shell: bash
working-directory: fincept-qt
run: |
set -euo pipefail
mkdir -p dist
# Copy main executable
cp build/Release/${{ matrix.exe }} dist/
# Copy qgeoview.dll (built by FetchContent, placed next to exe by post-build command)
if [ -f "build/Release/qgeoview.dll" ]; then
cp build/Release/qgeoview.dll dist/
echo "Copied qgeoview.dll"
else
echo "::warning::qgeoview.dll not found in build/Release — map widget may fail"
fi
# ----------------------------------------------------------------
# windeployqt
# Always an x64 host tool. For ARM64 cross-compiled builds
# QT_ROOT_DIR points to the ARM64 kit; windeployqt lives in x64.
# ----------------------------------------------------------------
WDQT="${QT_ROOT_DIR}/bin/windeployqt.exe"
if [ ! -f "$WDQT" ]; then
WDQT="${QT_ROOT_DIR}/../msvc2022_64/bin/windeployqt.exe"
fi
if [ -f "$WDQT" ]; then
echo "Running windeployqt: $WDQT"
"$WDQT" \
--release \
--no-translations \
--compiler-runtime \
--include-soft-plugins \
--force \
dist/${{ matrix.exe }}
else
echo "::warning::windeployqt not found — Qt DLLs not bundled"
fi
QT_BIN="${QT_ROOT_DIR}/bin"
QT_PLUGINS="${QT_ROOT_DIR}/plugins"
# ----------------------------------------------------------------
# Qt Core DLLs (windeployqt should handle these, but belt+suspenders)
# ----------------------------------------------------------------
for qt_dll in \
Qt6Core.dll Qt6Gui.dll Qt6Widgets.dll \
Qt6Charts.dll Qt6PrintSupport.dll \
Qt6Network.dll Qt6Sql.dll Qt6Concurrent.dll \
Qt6WebSockets.dll \
Qt6Multimedia.dll \
Qt6OpenGL.dll Qt6OpenGLWidgets.dll \
Qt6Svg.dll Qt6Xml.dll; do
if [ -f "${QT_BIN}/${qt_dll}" ] && [ ! -f "dist/${qt_dll}" ]; then
cp "${QT_BIN}/${qt_dll}" dist/
echo "Copied ${qt_dll}"
fi
done
# ----------------------------------------------------------------
# OpenSSL DLLs — Qt6::Network requires these for HTTPS.
# Qt 6.x ships them in its own bin/ directory.
# ----------------------------------------------------------------
for ssl_dll in \
libssl-3-x64.dll libcrypto-3-x64.dll \
libssl-3.dll libcrypto-3.dll \
libssl-1_1-x64.dll libcrypto-1_1-x64.dll; do
if [ -f "${QT_BIN}/${ssl_dll}" ] && [ ! -f "dist/${ssl_dll}" ]; then
cp "${QT_BIN}/${ssl_dll}" dist/
echo "Copied ${ssl_dll}"
fi
done
# zlib — the app now links zlib STATICALLY (x64-windows-static-md), so
# there is no runtime zlib DLL to ship. This loop is a harmless fallback
# for any transitive dynamic-zlib need; ZLIB_ROOT is the static-md dir
# and has no bin/*.dll, so it normally copies nothing. See release.yml.
for zlib_dll in zlib1.dll; do
if [ -n "${ZLIB_ROOT:-}" ] && [ -f "${ZLIB_ROOT}/bin/${zlib_dll}" ] && [ ! -f "dist/${zlib_dll}" ]; then
cp "${ZLIB_ROOT}/bin/${zlib_dll}" dist/
echo "Copied ${zlib_dll}"
elif [ -f "${QT_BIN}/${zlib_dll}" ] && [ ! -f "dist/${zlib_dll}" ]; then
cp "${QT_BIN}/${zlib_dll}" dist/
echo "Copied ${zlib_dll}"
fi
done
# ----------------------------------------------------------------
# MSVC Runtime DLLs — --compiler-runtime covers most, but copy
# from System32 as a fallback for anything missed.
# msvcp170 / vcruntime140_2 are the VS2022 v17.x toolset DLLs.
# ----------------------------------------------------------------
for vcrt_dll in \
vcruntime140.dll vcruntime140_1.dll vcruntime140_2.dll \
msvcp140.dll msvcp140_1.dll msvcp140_2.dll \
msvcp170.dll \
concrt140.dll vccorlib140.dll; do
SYS32="/c/Windows/System32/${vcrt_dll}"
if [ -f "$SYS32" ] && [ ! -f "dist/${vcrt_dll}" ]; then
cp "$SYS32" dist/
echo "Copied MSVC runtime ${vcrt_dll}"
fi
done
# ----------------------------------------------------------------
# Qt Platform Plugin — mandatory, app won't start without it
# ----------------------------------------------------------------
mkdir -p dist/platforms
for plat_dll in qwindows.dll qwindowsd.dll; do
if [ -f "${QT_PLUGINS}/platforms/${plat_dll}" ] && [ ! -f "dist/platforms/${plat_dll}" ]; then
cp "${QT_PLUGINS}/platforms/${plat_dll}" dist/platforms/
echo "Copied platforms/${plat_dll}"
fi
done
# ----------------------------------------------------------------
# Qt SQL Drivers — qsqlite required for SQLite database
# ----------------------------------------------------------------
mkdir -p dist/sqldrivers
for sql_dll in qsqlite.dll qsqlodbc.dll; do
if [ -f "${QT_PLUGINS}/sqldrivers/${sql_dll}" ] && [ ! -f "dist/sqldrivers/${sql_dll}" ]; then
cp "${QT_PLUGINS}/sqldrivers/${sql_dll}" dist/sqldrivers/
echo "Copied sqldrivers/${sql_dll}"
fi
done
# ----------------------------------------------------------------
# Qt Image Format Plugins — needed to render PNG/JPG/SVG/GIF/ICO
# in the UI (logos, icons, chart exports)
# ----------------------------------------------------------------
mkdir -p dist/imageformats
for img_dll in \
qbmp.dll qgif.dll qico.dll qjpeg.dll \
qpng.dll qsvg.dll qtga.dll qtiff.dll qwbmp.dll qwebp.dll; do
if [ -f "${QT_PLUGINS}/imageformats/${img_dll}" ] && [ ! -f "dist/imageformats/${img_dll}" ]; then
cp "${QT_PLUGINS}/imageformats/${img_dll}" dist/imageformats/
echo "Copied imageformats/${img_dll}"
fi
done
# ----------------------------------------------------------------
# Qt Icon Engine Plugin — renders .svg icons in toolbars/menus
# ----------------------------------------------------------------
mkdir -p dist/iconengines
if [ -f "${QT_PLUGINS}/iconengines/qsvgicon.dll" ] && [ ! -f "dist/iconengines/qsvgicon.dll" ]; then
cp "${QT_PLUGINS}/iconengines/qsvgicon.dll" dist/iconengines/
echo "Copied iconengines/qsvgicon.dll"
fi
# ----------------------------------------------------------------
# Qt Style Plugins — Fusion style (cross-platform look)
# ----------------------------------------------------------------
mkdir -p dist/styles
for style_dll in qwindowsvistastyle.dll; do
if [ -f "${QT_PLUGINS}/styles/${style_dll}" ] && [ ! -f "dist/styles/${style_dll}" ]; then
cp "${QT_PLUGINS}/styles/${style_dll}" dist/styles/
echo "Copied styles/${style_dll}"
fi
done
# ----------------------------------------------------------------
# Qt Network TLS Backend Plugin — required for HTTPS on Qt 6.2+
# (replaces old OpenSSL linkage model)
# ----------------------------------------------------------------
mkdir -p dist/tls
for tls_dll in qschannelbackend.dll qopensslbackend.dll qcertonlybackend.dll; do
if [ -f "${QT_PLUGINS}/tls/${tls_dll}" ] && [ ! -f "dist/tls/${tls_dll}" ]; then
cp "${QT_PLUGINS}/tls/${tls_dll}" dist/tls/
echo "Copied tls/${tls_dll}"
fi
done
# ----------------------------------------------------------------
# Qt Multimedia Plugins — audio/video backend (Windows MF + FFMPEG)
# ----------------------------------------------------------------
if [ -d "${QT_PLUGINS}/multimedia" ]; then
mkdir -p dist/multimedia
for media_dll in windowsmediafoundation.dll ffmpegmediaplugin.dll; do
if [ -f "${QT_PLUGINS}/multimedia/${media_dll}" ] && [ ! -f "dist/multimedia/${media_dll}" ]; then
cp "${QT_PLUGINS}/multimedia/${media_dll}" dist/multimedia/
echo "Copied multimedia/${media_dll}"
fi
done
fi
# ----------------------------------------------------------------
# Qt MultimediaWidgets DLL — required when HAS_QT_MULTIMEDIA is set
# (Qt6Multimedia.dll is in the core list above; this is the separate
# widgets integration DLL that windeployqt may miss)
# ----------------------------------------------------------------
if [ -f "${QT_BIN}/Qt6MultimediaWidgets.dll" ] && [ ! -f "dist/Qt6MultimediaWidgets.dll" ]; then
cp "${QT_BIN}/Qt6MultimediaWidgets.dll" dist/
echo "Copied Qt6MultimediaWidgets.dll"
fi
# ----------------------------------------------------------------
# Qt TextToSpeech DLL + SAPI plugin (Windows TTS backend)
# ----------------------------------------------------------------
if [ -f "${QT_BIN}/Qt6TextToSpeech.dll" ] && [ ! -f "dist/Qt6TextToSpeech.dll" ]; then
cp "${QT_BIN}/Qt6TextToSpeech.dll" dist/
echo "Copied Qt6TextToSpeech.dll"
fi
if [ -d "${QT_PLUGINS}/texttospeech" ]; then
mkdir -p dist/texttospeech
cp -n "${QT_PLUGINS}/texttospeech/"*.dll dist/texttospeech/ 2>/dev/null || true
echo "Copied texttospeech plugins"
fi
# ----------------------------------------------------------------
# Qt Network Information plugins (Qt 6.2+ — used by QNetworkInformation)
# ----------------------------------------------------------------
if [ -d "${QT_PLUGINS}/networkinformation" ]; then
mkdir -p dist/networkinformation
cp -n "${QT_PLUGINS}/networkinformation/"*.dll dist/networkinformation/ 2>/dev/null || true
echo "Copied networkinformation plugins"
fi
# ----------------------------------------------------------------
# Qt Platform Input Contexts — IME support (CJK, on-screen keyboard)
# ----------------------------------------------------------------
if [ -d "${QT_PLUGINS}/platforminputcontexts" ]; then
mkdir -p dist/platforminputcontexts
cp -n "${QT_PLUGINS}/platforminputcontexts/"*.dll dist/platforminputcontexts/ 2>/dev/null || true
echo "Copied platforminputcontexts plugins"
fi
# ----------------------------------------------------------------
# Qt Print Support Plugin — required by Qt6::PrintSupport
# (the DLL is in the core list; this is the Windows printer backend plugin)
# ----------------------------------------------------------------
if [ -d "${QT_PLUGINS}/printsupport" ]; then
mkdir -p dist/printsupport
cp -n "${QT_PLUGINS}/printsupport/"*.dll dist/printsupport/ 2>/dev/null || true
echo "Copied printsupport plugins"
fi
# ----------------------------------------------------------------
# Qt Generic Plugins (touch/tablet input)
# ----------------------------------------------------------------
if [ -d "${QT_PLUGINS}/generic" ]; then
mkdir -p dist/generic
cp -n "${QT_PLUGINS}/generic/"*.dll dist/generic/ 2>/dev/null || true
fi
# ----------------------------------------------------------------
# Direct3D compiler + OpenGL SW fallback
# Needed on machines without a proper GPU driver (VMs, headless CI).
# dxcompiler.dll + dxil.dll: Qt 6.6+ D3D12 backend (DXC shader compiler).
# ----------------------------------------------------------------
for d3d_dll in \
d3dcompiler_47.dll opengl32sw.dll libEGL.dll libGLESv2.dll \
dxcompiler.dll dxil.dll; do
if [ -f "${QT_BIN}/${d3d_dll}" ] && [ ! -f "dist/${d3d_dll}" ]; then
cp "${QT_BIN}/${d3d_dll}" dist/
echo "Copied ${d3d_dll}"
fi
done
# ----------------------------------------------------------------
# ICU DLLs — Qt 6 on Windows ships its own ICU for Unicode support
# ----------------------------------------------------------------
for icu_dll in "${QT_BIN}"/icu*.dll; do
base=$(basename "$icu_dll")
if [ -f "$icu_dll" ] && [ ! -f "dist/${base}" ]; then
cp "$icu_dll" dist/
echo "Copied ${base}"
fi
done
# ----------------------------------------------------------------
# qt.conf — pins plugin and data paths relative to the executable.
# Without this, Qt falls back to hardcoded build-time paths which
# don't exist on a clean machine and can cause plugin load failures.
# ----------------------------------------------------------------
printf '[Paths]\nPrefix = .\nPlugins = .\nLibraries = .\n' > dist/qt.conf
# ----------------------------------------------------------------
# Copy app resources and Python analytics scripts
# ----------------------------------------------------------------
cp -r resources dist/resources 2>/dev/null || true
bash .github/scripts/sync_scripts.sh scripts dist/scripts
# ----------------------------------------------------------------
# Final diagnostic — list everything that will be zipped
# ----------------------------------------------------------------
echo ""
echo "=== dist/ contents ($(find dist -type f | wc -l) files) ==="
find dist -type f | sort
# ---- Package: macOS ----
# Order is critical (see issue #139): Python.framework + resources must
# be placed INSIDE the .app BEFORE macdeployqt / codesign run. If we
# copy them as siblings of the .app (old behavior) the bundle can't find
# them at runtime; if we embed after signing, dyld on macOS 14+ kills
# the process with SIGKILL (Code Signature Invalid).
#
# On tag / workflow_dispatch: full stage -> macdeployqt -> deep ad-hoc
# codesign -> verify. On push builds: stage + skip macdeployqt/codesign
# (saves ~3-5 min; the resulting bundle won't run on stock macOS but is
# fine as a CI artifact for inspection).
#
# upload-artifact strips execute bits (zip limitation) so we tar dist/.
- name: Package (macOS)
if: runner.os == 'macOS'
working-directory: fincept-qt
run: |
set -euo pipefail
mkdir -p dist
MACDEPLOYQT="${QT_ROOT_DIR}/bin/macdeployqt"
IS_TAG="${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}"
# ── Locate or synthesize .app bundle ──────────────────────────────
if [ -d "build/${{ matrix.exe }}.app" ]; then
APP_BUNDLE="build/${{ matrix.exe }}.app"
else
APP_BUNDLE="build/${{ matrix.exe }}.app"
mkdir -p "${APP_BUNDLE}/Contents/MacOS"
cp "build/${{ matrix.exe }}" "${APP_BUNDLE}/Contents/MacOS/${{ matrix.exe }}"
chmod +x "${APP_BUNDLE}/Contents/MacOS/${{ matrix.exe }}"
# Version comes from CMakeLists.txt, never a literal — a hardcoded
# 4.3.0 here shipped a bundle whose CFBundleVersion disagreed with
# the binary the moment the project version moved, which breaks the
# auto-updater's version comparison. Heredoc delimiter is unquoted
# (<<PLIST, not <<'PLIST') so ${VERSION} interpolates.
VERSION=$(grep -Eo 'project\(FinceptTerminal VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | awk '{print $3}')
[ -n "${VERSION}" ] || { echo "::error::Could not parse VERSION from CMakeLists.txt"; exit 1; }
cat > "${APP_BUNDLE}/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleExecutable</key><string>FinceptTerminal</string>
<key>CFBundleIdentifier</key><string>com.fincept.terminal</string>
<key>CFBundleName</key><string>FinceptTerminal</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>${VERSION}</string>
<key>CFBundleVersion</key><string>${VERSION}</string>
<key>LSMinimumSystemVersion</key><string>13.0</string>
<key>NSHighResolutionCapable</key><true/>
</dict></plist>
PLIST
fi
# ── Stage resources INSIDE the bundle (not as siblings) ───────────
mkdir -p "${APP_BUNDLE}/Contents/Resources"
cp -r resources "${APP_BUNDLE}/Contents/Resources/" 2>/dev/null || true
bash .github/scripts/sync_scripts.sh scripts "${APP_BUNDLE}/Contents/Resources/scripts"
# ── Embed Python.framework (tag builds only — skip on push) ───────
if [ "$IS_TAG" = "true" ]; then
PY_FRAMEWORK=""
for candidate in \
"/Library/Frameworks/Python.framework" \
"$(brew --prefix python@3.12 2>/dev/null)/Frameworks/Python.framework" \
"$(brew --prefix python3 2>/dev/null)/Frameworks/Python.framework"; do
[ -d "${candidate}/Versions" ] && { PY_FRAMEWORK="${candidate}"; break; }
done
BUNDLE_FRAMEWORKS="${APP_BUNDLE}/Contents/Frameworks"
mkdir -p "${BUNDLE_FRAMEWORKS}"
if [ -n "${PY_FRAMEWORK}" ]; then
echo "Embedding Python.framework from: ${PY_FRAMEWORK}"
rsync -a --copy-unsafe-links "${PY_FRAMEWORK}/" "${BUNDLE_FRAMEWORKS}/Python.framework/" 2>/dev/null || \
cp -R "${PY_FRAMEWORK}" "${BUNDLE_FRAMEWORKS}/" 2>/dev/null || true
find "${BUNDLE_FRAMEWORKS}/Python.framework" -name "PrivateHeaders" -type l \
! -exec test -e {} \; -delete 2>/dev/null || true
PY_VER="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
ln -sf "../Frameworks/Python.framework/Versions/${PY_VER}/bin/python${PY_VER}" \
"${APP_BUNDLE}/Contents/MacOS/python3" 2>/dev/null || true
else
echo "::warning::Python.framework not found — bundle will rely on system python"
fi
fi
# ── Run macdeployqt on the fully staged bundle (tag builds only) ──
if [ "$IS_TAG" = "true" ] && [ -x "$MACDEPLOYQT" ]; then
echo "Running macdeployqt on ${APP_BUNDLE}"
"$MACDEPLOYQT" "${APP_BUNDLE}" -verbose=1 || \
echo "::warning::macdeployqt exited non-zero — continuing to codesign"
fi
# ── Deep ad-hoc codesign (tag builds only) — fix for issue #139 ───
# Strip every signature leaf-to-root, then re-sign whole bundle with
# the ad-hoc identity ("-"). Do NOT pass --preserve-metadata: there
# are no prior entitlements to preserve on a fresh bundle and the
# flag errors out on resource-fork detritus left by macdeployqt.
if [ "$IS_TAG" = "true" ]; then
echo "Stripping existing signatures..."
find "${APP_BUNDLE}" -type f \( -name "*.dylib" -o -name "*.so" -o -perm +111 \) 2>/dev/null | \
while read -r f; do
if file "$f" 2>/dev/null | grep -q "Mach-O"; then
codesign --remove-signature "$f" 2>/dev/null || true
fi
done
codesign --remove-signature "${APP_BUNDLE}" 2>/dev/null || true
echo "Re-signing bundle ad-hoc (deep)..."
codesign --force --deep --sign - --timestamp=none "${APP_BUNDLE}"
echo "Verifying signature..."
codesign --verify --deep --strict --verbose=2 "${APP_BUNDLE}" || {
echo "::error::Bundle verification failed"
codesign --display --deep --verbose=4 "${APP_BUNDLE}" || true
exit 1
}
fi
# ── Copy signed bundle to dist/ ──────────────────────────────────
cp -R "${APP_BUNDLE}" dist/
# Tar to preserve execute permissions (upload-artifact uses zip which strips them)
tar -czf dist.tar.gz -C dist .
# ---- Package: Linux (AppImage) ----
- name: Package (Linux)
if: runner.os == 'Linux'
working-directory: fincept-qt
run: |
set -euo pipefail
# ── Download linuxdeploy + Qt plugin ─────────────────────────────
wget -q "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
wget -q "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage
# ── Build AppDir structure ────────────────────────────────────────
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
cp build/${{ matrix.exe }} AppDir/usr/bin/
chmod +x AppDir/usr/bin/${{ matrix.exe }}
# Copy resources and scripts inside AppDir
cp -r resources AppDir/usr/bin/resources 2>/dev/null || true
bash .github/scripts/sync_scripts.sh scripts AppDir/usr/bin/scripts
# Desktop entry (required by linuxdeploy)
cat > AppDir/usr/share/applications/fincept-terminal.desktop <<'EOF'
[Desktop Entry]
Name=Fincept Terminal
Exec=FinceptTerminal
Icon=fincept-terminal
Type=Application
Categories=Finance;
EOF
# App icon — use real icon if present, otherwise generate a valid
# 256×256 PNG via Python/Pillow (always present on ubuntu-latest runners).
# We avoid ImageMagick because its default security policy and
# missing-font fallbacks produced zero-byte PNGs that failed libpng's
# CRC check in linuxdeploy. `python3 -c` sidesteps the YAML-indented
# heredoc trap (Python rejects the leading spaces as IndentationError).
ICON_DEST="AppDir/usr/share/icons/hicolor/256x256/apps/fincept-terminal.png"
if [ -f "resources/icons/app_icon_256.png" ]; then
cp resources/icons/app_icon_256.png "${ICON_DEST}"
else
python3 -c 'import sys; from PIL import Image, ImageDraw, ImageFont; img=Image.new("RGBA",(256,256),(26,26,46,255)); d=ImageDraw.Draw(img); f=ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",96) if __import__("os").path.exists("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf") else ImageFont.load_default(); d.text((128,128),"FT",fill=(255,255,255,255),font=f,anchor="mm"); img.save(sys.argv[1],"PNG")' "${ICON_DEST}"
fi
# Verify the file is a valid PNG before handing it to linuxdeploy.
file "${ICON_DEST}"
[ -s "${ICON_DEST}" ] || { echo "::error::Icon is empty or missing"; exit 1; }
# ── Strip unused Qt SQL driver plugins ───────────────────────────
# We only use SQLite. linuxdeploy-plugin-qt walks every plugin in
# Qt's sqldrivers/ folder and tries to resolve its dependencies —
# the Mimer plugin pulls in libmimerapi.so, which isn't installed
# on the runner and causes the deploy step to fail.
QT_SQLDRIVERS="${QT_ROOT_DIR:-$(dirname $(which qmake6 2>/dev/null || which qmake))/..}/plugins/sqldrivers"
if [ -d "${QT_SQLDRIVERS}" ]; then
rm -f "${QT_SQLDRIVERS}/libqsqlmimer.so" \
"${QT_SQLDRIVERS}/libqsqlodbc.so" \
"${QT_SQLDRIVERS}/libqsqlpsql.so" \
"${QT_SQLDRIVERS}/libqsqlmysql.so"
fi
# ── Build AppImage ────────────────────────────────────────────────
# UPDATE_INFORMATION embeds zsync update URL so AppImageUpdate can
# fetch deltas automatically from the GitHub release.
export QMAKE=$(which qmake6 || which qmake)
export OUTPUT="FinceptTerminal-Linux-x86_64.AppImage"
export UPDATE_INFORMATION="gh-releases-zsync|Fincept-Corporation|FinceptTerminal|latest|FinceptTerminal-Linux-x86_64.AppImage.zsync"
# ── OpenSSL must be bundled as a MATCHED PAIR (#352) ───────────────
# The app only calls libcrypto APIs, so ld's --as-needed drops
# libssl.so.3 from DT_NEEDED and linuxdeploy (which walks DT_NEEDED)
# bundled libcrypto.so.3 alone. Qt then dlopen()s the HOST libssl.so.3,
# which needs OPENSSL_3.3.0 from the stale bundled libcrypto 3.0.2:
# qt.tlsbackend.ossl: Failed to load libssl/libcrypto → all HTTPS dies.
# Keep this in sync with the same block in release.yml.
resolve_soname() {
local soname="$1" path
path=$(ldconfig -p | awk -v s="${soname}" '$1 == s && /x86-64/ { print $NF; exit }')
if [ -z "${path}" ] || [ "$(basename "${path}")" != "${soname}" ]; then
path="/usr/lib/x86_64-linux-gnu/${soname}"
fi
if [ ! -e "${path}" ]; then
echo "::error::${soname} not found on the runner — cannot bundle a consistent OpenSSL pair (#352)" >&2
exit 1
fi
printf '%s' "${path}"
}
OPENSSL_ARGS=()
for _so in libssl.so.3 libcrypto.so.3; do
_path=$(resolve_soname "${_so}")
echo "Bundling OpenSSL library: ${_path}"
OPENSSL_ARGS+=(--library "${_path}")
done
./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--plugin qt \
--output appimage \
"${OPENSSL_ARGS[@]}" \
2>&1 || true
# ── Guard: never ship a half-bundled OpenSSL again (#352) ──────────
SSL_BUNDLED=$(find AppDir -name 'libssl.so.3' -print -quit)
CRYPTO_BUNDLED=$(find AppDir -name 'libcrypto.so.3' -print -quit)
if [ -z "${SSL_BUNDLED}" ] || [ -z "${CRYPTO_BUNDLED}" ]; then
echo "::error::AppImage must bundle libssl.so.3 AND libcrypto.so.3 (ssl='${SSL_BUNDLED}' crypto='${CRYPTO_BUNDLED}') — a bundled libcrypto next to the host libssl breaks all TLS (#352)"
exit 1
fi
if LD_LIBRARY_PATH="$(dirname "${SSL_BUNDLED}")" ldd "${SSL_BUNDLED}" \
| grep -q 'libcrypto\.so\.3 => not found'; then
echo "::error::Bundled libssl.so.3 cannot resolve the bundled libcrypto.so.3"
LD_LIBRARY_PATH="$(dirname "${SSL_BUNDLED}")" ldd "${SSL_BUNDLED}" || true
exit 1
fi
echo "OpenSSL pair OK: ${SSL_BUNDLED} + ${CRYPTO_BUNDLED}"
TLS_PLUGIN=$(find AppDir -name 'libqopensslbackend.so' -print -quit)
if [ -z "${TLS_PLUGIN}" ]; then
echo "::error::Qt TLS backend plugin (libqopensslbackend.so) missing from AppDir — HTTPS would fail at runtime"
exit 1
fi
echo "Qt TLS plugin OK: ${TLS_PLUGIN}"
# Verify AppImage was created
if [ ! -f "$OUTPUT" ]; then
# Fallback: find any AppImage produced
OUTPUT=$(find . -maxdepth 1 -name "*.AppImage" | head -1)
fi
if [ -z "$OUTPUT" ] || [ ! -f "$OUTPUT" ]; then
echo "::error::AppImage build failed — no .AppImage produced"
exit 1
fi
echo "AppImage produced: $OUTPUT ($(du -sh "$OUTPUT" | cut -f1))"
# Rename to canonical name and move to dist/
mkdir -p dist
mv "$OUTPUT" dist/FinceptTerminal-Linux-x86_64.AppImage
chmod +x dist/FinceptTerminal-Linux-x86_64.AppImage
# Generate zsync file for delta updates
if command -v zsyncmake &>/dev/null; then
zsyncmake dist/FinceptTerminal-Linux-x86_64.AppImage -o dist/FinceptTerminal-Linux-x86_64.AppImage.zsync
echo "zsync file generated"
else
sudo apt-get install -y zsync -qq 2>/dev/null || true
if command -v zsyncmake &>/dev/null; then
zsyncmake dist/FinceptTerminal-Linux-x86_64.AppImage -o dist/FinceptTerminal-Linux-x86_64.AppImage.zsync
echo "zsync file generated"
else
echo "::warning::zsyncmake not available — delta updates disabled"
fi
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
# macOS: upload the tar to preserve execute permissions; others upload dist/ directly
path: ${{ runner.os == 'macOS' && 'fincept-qt/dist.tar.gz' || 'fincept-qt/dist/' }}
retention-days: 30
# ---- macOS Universal Binary ----
macos-universal:
needs: build
runs-on: macos-15
name: macOS Universal
# Only run if ALL build matrix jobs succeeded (not just "didn't fail")
if: "!cancelled() && needs.build.result == 'success'"
steps:
- name: Download macOS arm64
uses: actions/download-artifact@v4
with:
name: FinceptTerminal-macOS-arm64
path: arm64-dl
- name: Download macOS x64
uses: actions/download-artifact@v4
with:
name: FinceptTerminal-macOS-x64
path: x64-dl
- name: Extract macOS artifacts
run: |
# Artifacts were uploaded as dist.tar.gz to preserve execute permissions
mkdir -p arm64 x64
tar -xzf arm64-dl/dist.tar.gz -C arm64
tar -xzf x64-dl/dist.tar.gz -C x64
- name: Create Universal Binary
run: |
# Locate the binary — may be inside a .app bundle or at the top level
find_bin() {
local base="$1"
if [ -f "$base/FinceptTerminal" ]; then
echo "$base/FinceptTerminal"
elif [ -f "$base/FinceptTerminal.app/Contents/MacOS/FinceptTerminal" ]; then
echo "$base/FinceptTerminal.app/Contents/MacOS/FinceptTerminal"
else
echo "::error::Cannot find FinceptTerminal binary under $base" >&2
find "$base" -type f | head -20 >&2
exit 1
fi
}
ARM64_BIN=$(find_bin arm64)
X64_BIN=$(find_bin x64)
echo "arm64 binary: $ARM64_BIN"
echo "x64 binary: $X64_BIN"
# Verify the two binaries are actually different architectures before lipo
ARM64_ARCH=$(lipo -archs "$ARM64_BIN")
X64_ARCH=$(lipo -archs "$X64_BIN")
echo "arm64 artifact arch: $ARM64_ARCH"
echo "x64 artifact arch: $X64_ARCH"
if [ "$ARM64_ARCH" = "$X64_ARCH" ]; then
echo "::error::Both binaries report the same architecture ($ARM64_ARCH) — cannot create universal binary" >&2
exit 1
fi
mkdir -p dist
lipo -create "$ARM64_BIN" "$X64_BIN" -output dist/FinceptTerminal
chmod +x dist/FinceptTerminal
cp -r arm64/resources dist/resources 2>/dev/null || true
bash .github/scripts/sync_scripts.sh arm64/scripts dist/scripts
file dist/FinceptTerminal
lipo -info dist/FinceptTerminal
ls -lh dist/FinceptTerminal
# Tar to preserve execute permissions
tar -czf dist.tar.gz -C dist .
- name: Upload Universal artifact
uses: actions/upload-artifact@v4
with:
name: FinceptTerminal-macOS-universal
path: dist.tar.gz
retention-days: 30