-
Notifications
You must be signed in to change notification settings - Fork 20
984 lines (863 loc) · 40 KB
/
Copy pathinstall.yml
File metadata and controls
984 lines (863 loc) · 40 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
name: install
on:
push:
tags:
- "v*"
branches:
- "**"
paths:
- ".github/workflows/install.yml"
- "assets/**"
- "**.py"
pull_request:
branches:
- "**"
paths:
- ".github/workflows/install.yml"
- "assets/**"
- "**.py"
workflow_dispatch:
inputs:
deploy_beta:
type: boolean
description: "发布公测版"
default: false # 默认不勾选,避免误触
deploy_alpha:
type: boolean
description: "发布内测版"
default: false
ci_as_stable:
type: boolean
description: "CI版本作为正式发布(非预发布)"
default: false
notice_text:
type: string
description: "⚠️ 说明 / Note"
required: false
default: "不勾选任何选项,手动触发即为CI发版"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.deploy_alpha || 'false' }}-${{ inputs.deploy_beta || 'false' }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
meta:
runs-on: ubuntu-latest
if: "github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message, '[deploy-sync]')"
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- id: set_tag
# 通过环境变量传递 Commit Message,防止反引号注入攻击
env:
HEAD_COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
# 使用环境变量获取引用信息
is_tag_push=false
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
is_tag_push=true
fi
# 获取提交信息(用于检测 [deploy-beta])
commit_message="$HEAD_COMMIT_MSG"
if [ "$is_tag_push" = true ]; then
# 正式标签:直接使用标签名
tag="${GITHUB_REF#refs/tags/}"
version_type="release"
else
# 获取最新的正式版本标签
base_tag=$(git tag -l "v*" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
# 如果没有找到纯版本号标签,使用默认值
if [ -z "$base_tag" ] || [[ "$base_tag" != v*.*.* ]]; then
base_tag="v0.0.0"
fi
commit_short=$(git rev-parse --short HEAD)
date_suffix=$(date +"%y%m%d")
# 判断版本类型
# 优先判断 Alpha
if ([ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ github.event.inputs.deploy_alpha }}" = "true" ]) || echo "$commit_message" | tail -n1 | grep -q "\[deploy-alpha\]"; then
# 内测版:在基础版本上增加 2 个补丁版本号 (Base + 2)
# 修改点:将 $3+1 改为 $3+2
incremented_tag=$(echo "$base_tag" | awk -F. '{printf "%s.%s.%s", $1, $2, $3+2}')
tag="${incremented_tag}-alpha.${date_suffix}.${commit_short}"
version_type="alpha"
# 原有的 Beta 判断
elif ([ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "${{ github.event.inputs.deploy_beta }}" = "true" ]) || echo "$commit_message" | tail -n1 | grep -q "\[deploy-beta\]"; then
# 公测版:在基础版本上增加 1 个补丁版本号 (Base + 1)
incremented_tag=$(echo "$base_tag" | awk -F. '{printf "%s.%s.%s", $1, $2, $3+1}')
tag="${incremented_tag}-beta.${date_suffix}.${commit_short}"
version_type="beta"
# 原有的 CI 判断 (else 分支)
else
tag="${base_tag}-ci.${date_suffix}.${commit_short}"
version_type="ci"
fi
fi
# 添加调试信息
echo "Base tag: $base_tag"
echo "Generated tag: $tag"
echo "Commit short: $commit_short"
echo "Date suffix: $date_suffix"
echo "Version type: $version_type"
echo "tag=$tag" | tee -a $GITHUB_OUTPUT
echo "is_tag_push=$is_tag_push" | tee -a $GITHUB_OUTPUT
echo "is_dev_version=$([ "$version_type" = "ci" ] && echo "true" || echo "false")" | tee -a $GITHUB_OUTPUT
echo "version_type=$version_type" | tee -a $GITHUB_OUTPUT
# 2. 下载资源 (用于探测)
- name: Parse Configuration
id: parse_config
run: |
python3 -c "
import re, os, sys
req_file = 'requirements.txt'
mfaa_tag = ''
if os.path.exists(req_file):
with open(req_file, 'r', encoding='utf-8') as f:
for line in f:
m = re.match(r'^#\s*MFAA_TAG=(v[\w\.\-]+)', line.strip())
if m: mfaa_tag = m.group(1); break
if not mfaa_tag:
print('::error::未找到 # MFAA_TAG 配置'); sys.exit(1)
print(f'✅ 目标资源版本: {mfaa_tag}')
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write(f'mfaa_tag={mfaa_tag}\n')
"
# [修改] 下载资源 (改用 steps.parse_config.outputs.mfaa_tag)
- name: Download MFAA for Detection
uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1
with:
repository: MaaXYZ/MFAAvalonia
latest: false
tag: ${{ steps.parse_config.outputs.mfaa_tag }}
fileName: "MFAAvalonia-*-linux-x64*"
out-file-path: "temp_detect"
extract: true
# 3. 探测内核版本
- name: Detect Core Version
id: detect_maa
run: |
# 1. 使用 find 查找文件的【绝对路径】(加上 $(pwd))
SO_FILE=$(find "$(pwd)/temp_detect" -name "libMaaFramework.so" | head -n 1)
if [ -z "$SO_FILE" ]; then
echo "::error::根本找不到 libMaaFramework.so 文件!"
exit 1
fi
SO_DIR=$(dirname "$SO_FILE")
echo "📍 定位到库文件: $SO_FILE"
echo "🔧 库目录: $SO_DIR"
# 2. 【关键】设置 LD_LIBRARY_PATH 为绝对路径
# 这告诉 Linux:"加载库的时候,去这个目录里找依赖!"
export LD_LIBRARY_PATH="$SO_DIR:$LD_LIBRARY_PATH"
# 3. 【调试神器】运行 ldd
# 如果后面 Python 报错,看这部分日志就知道缺哪个库了
echo "=== 🕵️♂️ 依赖检查 (ldd) ==="
ldd "$SO_FILE" || true
echo "========================="
# 4. 生成探测脚本
cat << 'EOF' > detect.py
import sys, ctypes, os
# 从环境变量获取刚才找到的绝对路径
so_path = os.environ.get("TARGET_SO_PATH")
print(f"🐍 Python loading: {so_path}")
try:
# mode=ctypes.RTLD_GLOBAL 有时能解决跨库符号依赖问题
lib = ctypes.CDLL(so_path, mode=ctypes.RTLD_GLOBAL)
lib.MaaVersion.restype = ctypes.c_char_p
ver = lib.MaaVersion().decode('utf-8')
print(f"✅ Detected Core: {ver}")
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
f.write(f"version={ver}\n")
except Exception as e:
print(f"::error::加载失败: {e}")
sys.exit(1)
EOF
# 5. 运行脚本 (传入路径变量)
export TARGET_SO_PATH="$SO_FILE"
python3 detect.py
outputs:
tag: ${{ steps.set_tag.outputs.tag }}
is_tag_push: ${{ steps.set_tag.outputs.is_tag_push }}
is_dev_version: ${{ steps.set_tag.outputs.is_dev_version }}
version_type: ${{ steps.set_tag.outputs.version_type }}
maa_version: ${{ steps.detect_maa.outputs.version }}
mfaa_tag: ${{ steps.parse_config.outputs.mfaa_tag }}
resource_check:
name: Check Resources
needs: meta
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"
- name: Install maafw
env:
TARGET_VERSION: ${{ needs.meta.outputs.maa_version }}
run: |
python -m pip install --upgrade pip
echo "📦 Installing maafw==$TARGET_VERSION (Linux)..."
python -m pip install maafw=="$TARGET_VERSION" json-with-comments
- name: Restore Assets (Clone from Resource Repo)
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
repository: sunyink/MFABD2-Assets
ref: master
path: temp_assets
fetch-depth: 1
- name: Merge Assets
shell: bash
run: |
mkdir -p assets/MaaCommonAssets
cp -r temp_assets/MaaCommonAssets/* assets/MaaCommonAssets/
rm -rf temp_assets
- name: Check Resource
run: |
# 赋予执行权限,防止 Linux 下报错
chmod +x ./check_resource.py
python ./check_resource.py ./assets/
install:
needs: [meta, resource_check]
# PR 只跑资源检查,不跑完整构建
if: github.event_name != 'pull_request'
# 动态选择运行环境:Win跑Win,Mac跑Mac,Linux/安卓跑Ubuntu
runs-on: ${{ matrix.runner }}
env:
MAA_VERSION: ${{ needs.meta.outputs.maa_version }}
MFAA_TAG: ${{ needs.meta.outputs.mfaa_tag }}
# 解决 Windows 乱码问题
PYTHONUTF8: 1
PYTHONIOENCODING: utf-8
# 解决部分 Windows 终端不认 ANSI 颜色的问题(可选)
TERM: xterm
strategy:
fail-fast: false
matrix:
include:
# -------------------------------------------------------------------
# 🪟 Windows (x86_64) - 使用 Python 3.10
# -------------------------------------------------------------------
- os: win
arch: x86_64
runner: windows-latest
mfa_os: "win"
mfa_arch: "x64"
# Windows 专用配置
embed_version: "3.10.11" # Python 嵌入包版本
py_ver_short: "3.10" # 用于 pip 参数
py_arch: "amd64" # Python 官网文件名后缀
pip_plat: "win_amd64" # pip 平台标签
numpy_req: "numpy<2"
is_android: false
# -------------------------------------------------------------------
# 🪟 Windows (ARM64) - 使用 Python 3.11 (原生支持更好)
# -------------------------------------------------------------------
- os: win
arch: aarch64
runner: windows-latest
mfa_os: "win"
mfa_arch: "arm64"
# Windows 专用配置 (切到 3.11)
embed_version: "3.11.9"
py_ver_short: "3.11"
py_arch: "arm64"
pip_plat: "win_arm64"
numpy_req: "numpy>=2" # winarm64 没有<2的预编译包.
is_android: false
# -------------------------------------------------------------------
# 🍎 macOS (x64)
# -------------------------------------------------------------------
- os: macos
arch: x86_64
runner: macos-latest
mfa_os: "osx"
mfa_arch: "x64"
numpy_req: "numpy<2"
is_android: false
standalone_url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13+20240107-x86_64-apple-darwin-install_only.tar.gz"
standalone_sha256: "47da6831e269ad6af603f59bbd4767636c5749bd70a24363c9cf003c32c8ecfc"
# -------------------------------------------------------------------
# 🍎 macOS (ARM64)
# -------------------------------------------------------------------
- os: macos
arch: aarch64
runner: macos-latest
mfa_os: "osx"
mfa_arch: "arm64"
numpy_req: "numpy<2"
is_android: false
standalone_url: "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.10.13+20240107-aarch64-apple-darwin-install_only.tar.gz"
standalone_sha256: "4d19a0509c274f360a5c250d71a2af7263ebaf898ecdb71427f3532cbc6b2cf7"
# -------------------------------------------------------------------
# 🐧 Linux (x64)
# -------------------------------------------------------------------
- os: linux
arch: x86_64
runner: ubuntu-latest
mfa_os: "linux"
mfa_arch: "x64"
numpy_req: "numpy<2"
is_android: false
# -------------------------------------------------------------------
# 🐧 Linux (ARM64)
# -------------------------------------------------------------------
- os: linux
arch: aarch64
runner: ubuntu-latest
mfa_os: "linux"
mfa_arch: "arm64"
numpy_req: "numpy<2"
is_android: false
# -------------------------------------------------------------------
# 🤖 Android (ARM64) - 特殊逻辑
# -------------------------------------------------------------------
- os: android
arch: aarch64
runner: ubuntu-latest
is_android: true
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
submodules: true
# 设置构建环境的 Python (用于运行 install.py 等脚本)
# 这里的版本不影响 Windows 打包进去的嵌入式 Python 版本
- name: Set up Build Environment
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"
cache: "" # 禁用缓存,避免 macOS 上反序列化失败警告
# 显式指定 bash,确保在 Windows 上也能用 cp/rm/mkdir 等命令
- name: Install Build Dependencies
shell: bash
run: |
python -m pip install --upgrade pip
pip install requests json-with-comments
# ----------------------------------------------------------------
# 📦 资源下载 (根据 Matrix 变量动态处理)
# ----------------------------------------------------------------
# [桌面端] 下载 MFAAvalonia
- name: Download MFAAvalonia
if: matrix.is_android == false
uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1
with:
repository: MaaXYZ/MFAAvalonia
latest: false
tag: ${{ env.MFAA_TAG }}
# 直接使用 Matrix 里的变量,无需复杂判断
fileName: "MFAAvalonia-*-${{ matrix.mfa_os }}-${{ matrix.mfa_arch }}*"
out-file-path: "MFA"
extract: true
# [安卓端] 下载 MaaFramework Core
- name: Download MaaFramework (Android)
if: matrix.is_android == true
uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1
with:
repository: MaaXYZ/MaaFramework
latest: false
tag: "${{ env.MAA_VERSION }}"
# Android 文件名格式通常是 MAA-android-arm64-xxxx
fileName: "MAA-android-${{ matrix.arch }}*"
out-file-path: "AndroidCore"
extract: true
- name: Restore Assets (Clone from Resource Repo)
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
repository: sunyink/MFABD2-Assets
ref: master
path: temp_assets
fetch-depth: 1
- name: Merge Assets
shell: bash
run: |
# 创建目标目录结构(如果不存在)
mkdir -p assets/MaaCommonAssets
# 解开 MaaCommonAssets/MaaCommonAssets 套娃
cp -r temp_assets/MaaCommonAssets/* assets/MaaCommonAssets/
rm -rf temp_assets
echo "✅ 资源合并完成"
# ----------------------------------------------------------------
# Windows 专属:嵌入式 Python 打包
# ----------------------------------------------------------------
- name: Setup Embed Python (Windows)
if: matrix.os == 'win'
shell: bash
run: |
echo "⬇️ Downloading Python Embed ${{ matrix.embed_version }} for ${{ matrix.py_arch }}..."
# 1. 下载:利用 Matrix 变量 (支持 3.10 和 3.11)
curl -L -o python_embed.zip "https://www.python.org/ftp/python/${{ matrix.embed_version }}/python-${{ matrix.embed_version }}-embed-${{ matrix.py_arch }}.zip"
# 2. 解压 (Windows Runner 自带 7z)
mkdir -p install/python
7z x python_embed.zip -oinstall/python
# 开启 site 支持
python3 -c "import pathlib; p = next(pathlib.Path('install/python').glob('*._pth')); p.write_text(p.read_text().replace('#import site', 'import site'))"
# 3. 开启 site 支持
# 注意:这里我们构造 python3x._pth 文件名,因为 3.10 是 python310._pth,3.11 是 python311._pth
# 简单的做法是通配符,或者用 Python 脚本找
python3 -c "
import pathlib
import re
pth_file = next(pathlib.Path('install/python').glob('python*._pth'))
print(f'Modifying {pth_file}')
pth_file.write_text(pth_file.read_text().replace('#import site', 'import site'))
"
echo "📦 Installing Dependencies for ${{ matrix.pip_plat }} (Python ${{ matrix.py_ver_short }})..."
# 4. 重点:安装依赖
# 使用 --only-binary=:all: 强制下载 Wheel 包 (这对 Pillow/Numpy 在 Windows 上至关重要)
# 使用 Python 精准剔除 numpy,防止误伤注释或其他包
python3 -c "
import re
with open('requirements.txt', 'r') as f_in, open('requirements_no_numpy.txt', 'w') as f_out:
for line in f_in:
# 正则解释:
# ^\s* : 行首允许有空格
# numpy : 必须是 numpy
# \s* : 允许 numpy 后面有空格
# ([<>=!~;]|$) : 后面必须跟着版本比较符、分号,或者是行尾
if not re.match(r'^\s*numpy\s*([<>=!~;]|$)', line, re.IGNORECASE):
f_out.write(line)
"
python3 -m pip install \
--platform ${{ matrix.pip_plat }} \
--python-version ${{ matrix.py_ver_short }} \
--only-binary=:all: \
--no-cache-dir \
--target install/python/Lib/site-packages \
"${{ matrix.numpy_req }}" \
-r requirements_no_numpy.txt \
maafw==$MAA_VERSION
# ----------------------------------------------------------------
# [新增] macOS 专属:Portable Python 打包
# ----------------------------------------------------------------
- name: Setup Portable Python (macOS)
if: matrix.os == 'macos'
shell: bash
run: |
echo "⬇️ Downloading Portable Python for macOS..."
# 失败重试和SHA校验
curl -fL --retry 3 --retry-delay 2 -o python_mac.tar.gz "${{ matrix.standalone_url }}"
echo "${{ matrix.standalone_sha256 }} python_mac.tar.gz" | shasum -a 256 -c -
# 解压 (standalone 包结构通常是 python/install/...)
mkdir -p temp_python
tar -xzf python_mac.tar.gz -C temp_python
# 💡 调试输出一下目录结构,防止以后包结构又变
echo "📂 预计解压后的目录结构如下:"
ls -la temp_python/python/
# 移动到 install/python
mkdir -p install/python
cp -r temp_python/python/* install/python/
rm -rf temp_python python_mac.tar.gz
# 设置权限 (非常重要)
chmod +x install/python/bin/python3
echo "📦 Installing Dependencies (macOS)..."
PY_EXEC="install/python/bin/python3"
# 剔除 numpy
"$PY_EXEC" -c "
import re
with open('requirements.txt', 'r') as f_in, open('requirements_no_numpy.txt', 'w') as f_out:
for line in f_in:
if not re.match(r'^\s*numpy\s*([<>=!~;]|$)', line, re.IGNORECASE):
f_out.write(line)
"
# 安装依赖到便携版环境
"$PY_EXEC" -m pip install --upgrade pip
"$PY_EXEC" -m pip install "${{ matrix.numpy_req }}" -r requirements_no_numpy.txt maafw==$MAA_VERSION
# 清理缓存
"$PY_EXEC" -m pip cache purge
find install/python -name "__pycache__" -type d -exec rm -rf {} +
# 🔧 macOS 便携 Python 的 pip 安装时可能丢失 wheel 内的 .dylibs 目录
# (如 numpy/.dylibs/libopenblas64_.0.dylib, PIL/.dylibs/libtiff.6.dylib 等)
# 此处扫描所有 .so 文件的 @loader_path 依赖,缺失则从对应 wheel 中提取补回
echo "🔍 Verifying native dylibs..."
"$PY_EXEC" -c "
import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata, sysconfig
from pathlib import Path
site = Path(sysconfig.get_path('purelib'))
if not site.exists():
sys.exit(0)
# 导入名 -> PyPI 分发名的映射 (部分包的目录名与分发名不一致)
DIS_NAME = {
'PIL': 'Pillow',
'cv2': 'opencv-python-headless',
}
try:
for dist, names in importlib.metadata.packages_distributions().items():
for n in names:
DIS_NAME.setdefault(n, dist)
except Exception:
pass
def resolve_dist(pkg):
'''目录名 -> PyPI 分发名, 多级回退'''
for k, v in DIS_NAME.items():
if k.lower() == pkg.lower():
return v
return pkg
broken = {} # pkg_dir -> {'dist': dist_name, 'dylibs': set()}
for so in site.rglob('*.so'):
try:
out = subprocess.check_output(['otool', '-L', str(so)], text=True)
except Exception:
continue
for line in out.split('\n'):
m = re.match(r'\s+(@loader_path/\S+)', line)
if not m:
continue
ref = m.group(1)
resolved = (so.parent / ref.replace('@loader_path/', '')).resolve()
if not resolved.exists():
pkg = so.relative_to(site).parts[0]
broken.setdefault(pkg, {'dist': resolve_dist(pkg), 'dylibs': set()})
broken[pkg]['dylibs'].add(os.path.basename(ref))
if not broken:
print('✅ All native dylibs present')
sys.exit(0)
print(f'⚠️ {len(broken)} package(s) with missing dylibs, repairing...')
plat = sysconfig.get_platform().replace('-', '_').replace('.', '_')
py_ver = f'{sys.version_info.major}.{sys.version_info.minor}'
for pkg, info in sorted(broken.items()):
dist_name = info['dist']
try:
ver = importlib.metadata.version(dist_name)
except Exception:
print(f' ⚠️ Skip {pkg} (dist: {dist_name}): cannot determine version')
continue
print(f' 📦 {pkg} -> {dist_name}=={ver}')
with tempfile.TemporaryDirectory() as tmp:
result = subprocess.run([
sys.executable, '-m', 'pip', 'download',
f'{dist_name}=={ver}', '--no-deps', '--dest', tmp,
'--platform', plat, '--python-version', py_ver,
'--only-binary=:all:', '--no-cache-dir',
], capture_output=True, text=True)
if result.returncode != 0:
print(f' ⚠️ pip download failed: {result.stderr.strip()}')
continue
whls = [f for f in os.listdir(tmp) if f.endswith('.whl')]
if not whls:
print(f' ⚠️ No wheel found')
continue
with zipfile.ZipFile(os.path.join(tmp, whls[0])) as zf:
# 提取 wheel 内所有 .dylib (而非仅缺失的),
# 确保同包的多个 dylib 版本一致, 避免部分替换导致 ABI 不匹配
names = [n for n in zf.namelist() if n.endswith('.dylib')]
if not names:
print(f' ℹ️ No dylibs in wheel')
continue
dst = site / pkg / '.dylibs'
os.makedirs(dst, exist_ok=True)
for n in names:
zf.extract(n, tmp)
shutil.copy2(os.path.join(tmp, n), dst / os.path.basename(n))
os.chmod(dst / os.path.basename(n), 0o755)
print(f' ✅ {os.path.basename(n)}')
print('✅ Dylib repair complete')
"
# ----------------------------------------------------------------
# 🏗️ 构建与组装
# ----------------------------------------------------------------
- name: Install & Merge
shell: bash
run: |
# 1. 运行 install.py (生成配置、版本号等)
# install.py 需要处理好路径分隔符
python ./install.py ${{ needs.meta.outputs.tag }} ${{ matrix.os }} ${{ env.MAA_VERSION }}
# 2. [桌面端逻辑] 合并 MFAAvalonia
if [[ "${{ matrix.is_android }}" == "false" ]]; then
if [ -d "MFA" ]; then
mkdir -p install
# 这里的 cleanup 移到专门的步骤里做,防止 bash 前没删干净
# 为了稳健,先在源目录删一次
find MFA -name "*.zip" -delete 2>/dev/null || true
find MFA -name "*.tar.gz" -delete 2>/dev/null || true
# 使用 bash 覆盖
cp -rf MFA/* install/
fi
# 3. [安卓逻辑] 合并 MaaFramework Core
else
echo "🤖 处理 Android 内核合并..."
if [ -d "AndroidCore" ]; then
cp -r AndroidCore/* install/
echo "✅ Android 内核已合并"
else
echo "❌ 未找到 AndroidCore 目录"
exit 1
fi
fi
# [核心修复 1] 强力清理残留的压缩包
- name: Cleanup Archives
shell: bash
run: |
echo "🧹 Cleaning up zip/tar.gz files..."
rm -f *.zip *.tar.gz
rm -rf MFA AndroidCore
# 清理 install 目录里可能被拷进去的压缩包
find install -name "*.zip" ! -name "python*.zip" -delete 2>/dev/null || true
find install -name "*.tar.gz" -delete 2>/dev/null || true
echo "✅ Cleanup done."
# [核心修复 2] 原版逻辑:利用 Python 脚本清理不匹配的 Runtimes
- name: Prune Runtimes (Architecture Slimming)
if: matrix.is_android == false
shell: bash
run: |
python3 -c "
import shutil, os
from pathlib import Path
# 从 Matrix 获取目标特征
target_os = '${{ matrix.mfa_os }}' # win, osx, linux
target_arch = '${{ matrix.mfa_arch }}' # x64, arm64
print(f'🎯 Target Runtime: {target_os}-{target_arch}')
# 定义需要保留的关键词
keep_keywords = []
keep_keywords.append(target_os)
if target_arch == 'x64': keep_keywords.extend(['x64', 'amd64'])
else: keep_keywords.extend(['arm64', 'aarch64'])
runtimes_dir = Path('install/runtimes')
if runtimes_dir.exists():
for p in runtimes_dir.iterdir():
if not p.is_dir(): continue
# 逻辑:必须包含 OS,且必须包含架构
# 1. 检查是否匹配 OS (如果不包含 target_os 且包含其他os名字 -> 删)
name = p.name.lower()
# 这里做一个简化的强匹配逻辑:
# 只要文件夹名字里包含了 'win','linux','osx' 中的任意一个,它就必须包含 target_os
has_os_tag = any(k in name for k in ['win', 'linux', 'osx'])
if has_os_tag and target_os not in name:
shutil.rmtree(p); continue
# 2. 检查架构
has_arch_tag = any(k in name for k in ['x64', 'amd64', 'arm64', 'aarch64'])
# 检查是否包含我们要的架构
match_target_arch = any(k in name for k in keep_keywords if k in ['x64', 'amd64', 'arm64', 'aarch64'])
if has_arch_tag and not match_target_arch:
print(f'🗑️ Pruning mismatched arch: {name}')
shutil.rmtree(p)
"
- name: Inject Announcement & Verify
shell: bash
run: |
echo "::group::🚀 开始注入公告"
python scripts/inject_announcement.py "${{ needs.meta.outputs.tag }}"
echo "::endgroup::"
echo "::group::👀 验证注入结果"
TARGET="install/resource/Announcement/1.公告.md"
if [ -f "$TARGET" ]; then
AFTER_ANCHOR=$(grep -A 3 "Msg-Anch" "$TARGET" | tail -n +2 | tr -d '[:space:]')
if [ -n "$AFTER_ANCHOR" ]; then
echo "✅ 已注入通知"
echo "--- 注入内容预览 ---"
grep -A 20 "Msg-Anch" "$TARGET" | head -25
else
echo "ℹ️ 本次未注入(草稿为空 / 不在目标版本范围 / 等)"
fi
else
echo "::error::目标文件不存在: $TARGET"
exit 1
fi
echo "::endgroup::"
- name: Remove redundant MAA binaries (Win Only)
if: matrix.os == 'win'
shell: bash
run: |
# 仅针对 Windows,因为只有 Windows 下载了 Python 包
python3 -c "
import shutil
from pathlib import Path
root = Path('install')
# 1. 寻找源 plugins 目录 (在 python 包里)
src_plugins = next(root.rglob('**/site-packages/maa/bin/plugins'), None)
# 2. 寻找目标 native 目录 (在 runtimes 里)
# 假设 install/runtimes 下只有一个平台文件夹,或者我们只关心 native 层级
target_native = next(root.rglob('runtimes/*/native'), None)
# 3. 如果两者都存在,执行移动
if src_plugins and target_native and src_plugins.exists():
dest_plugins = target_native / 'plugins'
if not dest_plugins.exists():
print(f'Moving plugins from {src_plugins} to {dest_plugins}')
shutil.move(str(src_plugins), str(dest_plugins))
else:
print(f'Target plugins dir already exists: {dest_plugins}')
# 4. 安全删除所有冗余的 bin 目录
# 删除 install/python/.../maa/bin,因为 runtimes 里已经有了
for p in Path('install').rglob('**/site-packages/maa/bin'):
if p.is_dir():
print(f'Removing redundant bin: {p}')
shutil.rmtree(p)
"
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: MFABD2-${{ needs.meta.outputs.tag }}-${{ matrix.os }}-${{ matrix.is_android && 'arm64' || matrix.arch }}
path: "install"
include-hidden-files: true
changelog:
name: Generate changelog
runs-on: ubuntu-latest
needs: meta
outputs:
release_body: ${{ steps.set_release_body.outputs.content }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.x"
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install requests
# ✅ 使用新系统替换git-cliff
- name: Generate comprehensive changelog
id: merge_changelog
run: |
cd scripts
python changelog_generator.py
env:
CURRENT_TAG: ${{ needs.meta.outputs.tag }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
# 上传 CHANGES.md 到构建物
# =======================================================
- name: Upload Changelog Artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: CHANGES
path: CHANGES.md
# =======================================================
- name: Set release body
id: set_release_body
run: |
# 使用随机定界符防止内容包含 EOF 导致截断
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "content<<$EOF" >> $GITHUB_OUTPUT
cat CHANGES.md >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
release:
if: (needs.meta.outputs.is_tag_push == 'true') || contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha') || (github.event_name == 'workflow_dispatch' && contains(needs.meta.outputs.tag, '-ci'))
needs: [meta, install, changelog]
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- name: Get current timestamp
id: get_time
run: echo "time=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: artifacts
- name: Package release assets
run: |
cd artifacts
mkdir -p release_assets
for dir in *; do
# 直接使用目录名作为 zip 文件名
zip_name="${dir}.zip"
# 创建压缩包
(cd "$dir" && zip -r "../release_assets/$zip_name" .)
done
- name: Create GitHub Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
files: artifacts/release_assets/*.zip
tag_name: ${{ needs.meta.outputs.tag }}
name: "MFABD2 ${{ needs.meta.outputs.tag }}"
target_commitish: ${{ github.sha }}
body: |
${{ contains(needs.changelog.outputs.release_body, '## ') && needs.changelog.outputs.release_body || '### 无显著变更' }}
draft: false
prerelease: ${{ contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha') || (contains(needs.meta.outputs.tag, '-ci') && !(github.event_name == 'workflow_dispatch' && github.event.inputs.ci_as_stable == 'true')) }}
env: # 添加环境变量以支持高限额API调用
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger MirrorChyanUploading
if: github.repository_owner == 'sunyink' && ((needs.meta.outputs.is_tag_push == 'true') || contains(needs.meta.outputs.tag, '-beta') || contains(needs.meta.outputs.tag, '-alpha'))
shell: bash
env:
tag: ${{ needs.meta.outputs.tag }}
CHANGELOG_BODY: ${{ needs.changelog.outputs.release_body }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "安全触发镜像服务: 版本=$tag"
# 将 " 替换为 \"
ESCAPED_BODY="${CHANGELOG_BODY//\"/\\\"}"
# 触发包发布流程
gh workflow run --repo $GITHUB_REPOSITORY --ref ${{ github.ref }} mirrorchyan.yml -f tag=$tag
# 触发更新信息推送流程 (现在可以安全处理带引号的日志了)
gh workflow run --repo $GITHUB_REPOSITORY --ref ${{ github.ref }} mirrorchyan_release_note.yml -f tag=$tag -f body="$ESCAPED_BODY"
notify:
needs: [meta, install]
if: always() && needs.install.result == 'success' && (github.event_name == 'workflow_dispatch' || needs.meta.outputs.version_type != 'ci')
runs-on: ubuntu-latest
steps:
- name: Extract commit info
id: commit-info
env:
RAW_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
RAW_COMMIT_TIMESTAMP: ${{ github.event.head_commit.timestamp }}
run: |
# 调试:显示所有可用参数
echo "=== 调试信息 ==="
echo "GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME"
echo "HEAD_COMMIT_MESSAGE: $RAW_COMMIT_MESSAGE"
echo "HEAD_COMMIT_TIMESTAMP: $RAW_COMMIT_TIMESTAMP"
echo "GITHUB_SHA: $GITHUB_SHA"
# 设置默认值,直接引用环境变量
COMMIT_MSG="$RAW_COMMIT_MESSAGE"
if [ -z "$COMMIT_MSG" ]; then
COMMIT_MSG="手动触发工作流"
fi
COMMIT_TIMESTAMP="$RAW_COMMIT_TIMESTAMP"
if [ -z "$COMMIT_TIMESTAMP" ]; then
COMMIT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
fi
COMMIT_SHA="$GITHUB_SHA"
if [ -z "$COMMIT_SHA" ]; then
COMMIT_SHA="unknown"
fi
# 提取第一行作为摘要
SUBJECT=$(echo "$COMMIT_MSG" | head -n 1)
# 简化描述处理
if [ $(echo "$COMMIT_MSG" | wc -l) -gt 1 ]; then
BODY=$(echo "$COMMIT_MSG" | tail -n +2 | head -c 100 | tr -d '\n')
if [ ${#BODY} -eq 100 ]; then
BODY="$BODY..."
fi
else
BODY=""
fi
# 格式化时间戳为东八区时间
TIME=$(TZ='Asia/Shanghai' date -d "$COMMIT_TIMESTAMP" +"%Y-%m-%d %H:%M:%S")
SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7)
# 输出变量
echo "subject=$SUBJECT" >> $GITHUB_OUTPUT
echo "body=$BODY" >> $GITHUB_OUTPUT
echo "time=$TIME" >> $GITHUB_OUTPUT
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "=== 处理结果 ==="
echo "Subject: $SUBJECT"
echo "Body: $BODY"
echo "Time: $TIME"
echo "Short SHA: $SHORT_SHA"
- name: Send Notification
uses: Y2Nk4/qmsg-action@ce43bda34f1ba6668ff43ba8d0ad465d1c959723 # master
with:
# qq: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_QQ_DEV || secrets.QMSG_QQ_PUB }}
groups: ${{ needs.meta.outputs.version_type == 'ci' && secrets.QMSG_GROUPS_DEV || secrets.QMSG_GROUPS_PUB }}
# groups: ${{ secrets.QMSG_NOTIFY_GROUPS }}
key: ${{ secrets.QMSG_KEY }}
message: |
🏗️ [${{ github.repository }}] 构建完成
👤 提交者: ${{ github.actor }}
🌿 分支: ${{ github.ref_name }}
🏷️ 版本: ${{ needs.meta.outputs.tag }}
📦 类型: ${{ contains(needs.meta.outputs.tag, '-alpha') && '内测版' || (contains(needs.meta.outputs.tag, '-beta') && '公测版' || (contains(needs.meta.outputs.tag, '-ci') && '开发版' || '正式版')) }}
🆔 提交哈希: ${{ steps.commit-info.outputs.short_sha }}
⏱️ 提交时间: ${{ steps.commit-info.outputs.time }}
📝 摘要: ${{ steps.commit-info.outputs.subject }}
${{ steps.commit-info.outputs.body != '' && format('📖 描述: {0}', steps.commit-info.outputs.body) || '📖 描述: 无' }}
🔗 查看提交: https://github.com/${{ github.repository }}/commit/${{ github.sha }}
🔨 构建下载: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}