forked from Fincept-Corporation/FinceptTerminal
-
Notifications
You must be signed in to change notification settings - Fork 0
889 lines (801 loc) · 40.1 KB
/
Copy pathbuild-cpp.yml
File metadata and controls
889 lines (801 loc) · 40.1 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
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"
QT_MODULES: "qtcharts qtwebsockets qtmultimedia"
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 ----
- os: ubuntu-22.04
arch: x64
platform: Linux
artifact: FinceptTerminal-Linux-x64
exe: FinceptTerminal
qt_arch: ""
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.
- 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 \
qt6-base-dev qt6-base-private-dev \
qt6-tools-dev qt6-tools-dev-tools \
libqt6charts6-dev libqt6websockets6-dev \
qt6-multimedia-dev libqt6multimediawidgets6 \
libqt6svg6-dev libqt6concurrent6 \
libgl1-mesa-dev libglu1-mesa-dev \
libxkbcommon-dev libxkbcommon-x11-0 \
libxcb-cursor0 libxcb-icccm4 libxcb-image0 \
libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
libxcb-xinerama0 libxcb-xkb1 \
pkg-config zip
echo "CC=gcc-12" >> "$GITHUB_ENV"
echo "CXX=g++-12" >> "$GITHUB_ENV"
# ---- OpenSSL 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.
- name: Install OpenSSL (Windows)
if: runner.os == 'Windows'
shell: bash
run: |
set -euo pipefail
VCPKG_ROOT="${VCPKG_INSTALLATION_ROOT:-C:/vcpkg}"
echo "Using vcpkg at: $VCPKG_ROOT"
"$VCPKG_ROOT/vcpkg" install openssl:x64-windows --no-print-usage
OPENSSL_DIR="$VCPKG_ROOT/installed/x64-windows"
echo "OPENSSL_ROOT_DIR=$OPENSSL_DIR" >> "$GITHUB_ENV"
echo "OpenSSL installed at: $OPENSSL_DIR"
ls "$OPENSSL_DIR/lib/libcrypto.lib" && echo "libcrypto.lib confirmed"
# ---- Qt6 target (Windows/macOS) ----
- name: Install Qt6
if: runner.os != 'Linux'
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"
# ---- Configure CMake ----
- 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_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
${{ 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
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_PREFIX_PATH="${QT_ROOT_DIR}" \
${OPENSSL_ARG} \
${{ 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
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DCMAKE_PREFIX_PATH="${QT_ROOT_DIR}" \
-DQT_HOST_PATH="${HOST_QT}" \
-T ClangCL \
${OPENSSL_ARG} \
${{ 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))"
# ---- 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
# ----------------------------------------------------------------
# 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 }}"
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>4.0.2</string>
<key>CFBundleVersion</key><string>4.0.2</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"
./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--plugin qt \
--output appimage \
2>&1 || true
# 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
# ---- Release ----
release:
needs: [build, macos-universal]
runs-on: ubuntu-latest
name: Create Release
if: "!cancelled() && needs.build.result == 'success' && needs.macos-universal.result == 'success' && startsWith(github.ref, 'refs/tags/')"
permissions:
contents: write
steps:
- name: Install zip
run: sudo apt-get install -y zip
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Package releases
shell: bash
run: |
cd artifacts
for dir in */; do
name="${dir%/}"
if [[ "$name" == *"Windows"* ]]; then
# Windows artifacts are plain directories → zip
cd "$name"
zip -r "../../${name}.zip" .
cd ..
elif [[ "$name" == *"macOS"* ]]; then
# macOS artifacts are already a dist.tar.gz (execute permissions preserved)
mkdir -p "/tmp/${name}"
tar -xzf "${name}/dist.tar.gz" -C "/tmp/${name}"
tar -czf "../${name}.tar.gz" -C "/tmp/${name}" .
elif [[ "$name" == *"Linux"* ]]; then
# Linux: AppImage + optional zsync — copy directly, no re-archive
cp "${name}/FinceptTerminal-Linux-x86_64.AppImage" "../FinceptTerminal-Linux-x86_64.AppImage" 2>/dev/null || true
cp "${name}/FinceptTerminal-Linux-x86_64.AppImage.zsync" "../FinceptTerminal-Linux-x86_64.AppImage.zsync" 2>/dev/null || true
fi
done
ls -lh ../
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Fincept Terminal ${{ github.ref_name }}
make_latest: true
body: |
## Fincept Terminal ${{ github.ref_name }}
### Downloads
**Windows**
- `FinceptTerminal-Windows-x64.zip` — Windows x64 (MSVC)
- `FinceptTerminal-Windows-arm64.zip` — Windows ARM64
**Linux**
- `FinceptTerminal-Linux-x86_64.AppImage` — Linux x86_64 (portable, runs on any distro)
- `FinceptTerminal-Linux-x86_64.AppImage.zsync` — Delta update file (used by auto-updater)
**macOS**
- `FinceptTerminal-macOS-universal.tar.gz` — macOS Universal (ARM64 + Intel)
- `FinceptTerminal-macOS-arm64.tar.gz` — macOS Apple Silicon
- `FinceptTerminal-macOS-x64.tar.gz` — macOS Intel
files: |
FinceptTerminal-Windows-x64.zip
FinceptTerminal-Windows-arm64.zip
FinceptTerminal-Linux-x86_64.AppImage
FinceptTerminal-Linux-x86_64.AppImage.zsync
FinceptTerminal-macOS-arm64.tar.gz
FinceptTerminal-macOS-x64.tar.gz
FinceptTerminal-macOS-universal.tar.gz
fail_on_unmatched_files: false
draft: false
prerelease: false