Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
973 lines (910 loc) · 51 KB
/
Copy pathMakefile
File metadata and controls
973 lines (910 loc) · 51 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
# OpenCode for Termux - local build orchestrator
SHELL := /data/data/com.termux/files/usr/bin/bash
.DEFAULT_GOAL := $(if $(strip $(family)),family,help)
VER ?= latest
VERS ?=
PKG ?= both
PACKAGER_NAME ?= Adeloye Adetayo (Spectra010s) <adeloyeadetayo8@gmail.com>
MORE ?=
ODIR ?=
MIX ?= 0
# ── Packaging hook registry (version-ranged; resolved at packaging time) ──
# Hooks ship inside package maintainer scripts (postinst / .INSTALL) and are
# gated by the packager scripts via hook_enabled():
# enabled = name in HOOKS_DISABLE ? 0
# : name in HOOKS_ENABLE ? 1
# : VERSION matches the hook's default version-glob ? 1 : 0
# Registered hooks:
# stale-serve-kill — postinst/post_upgrade stops stale `serve` daemons of the
# upgraded binary (new TUI otherwise times out waiting for the background
# service, issue #17). Default scope: v2 native 2.0.[0-3] ONLY — upstream
# opencode 2.0.4+ resolves the stale-service handling itself; do not widen
# without checking upstream.
HOOKS_ENABLE ?=
HOOKS_DISABLE ?=
HOOK_STALE_SERVE_KILL_VERSIONS ?= 2.0.[0-3]
# Release upload target variables
TAG ?= Push$(shell date +%y%m%d)
REPO ?= Spectra010s/opencode-termux
# Release staging dir: /tmp is not app-writable on Termux; honor TMPDIR
RELEASE_DIR ?= $(if $(TMPDIR),$(TMPDIR),/tmp)/oc-release-$(TAG)
NATIVE ?=
VER_IS_SET = $(filter-out file default,$(origin VER))
NATIVE_VER = $(if $(VER_IS_SET),$(VER),$(VERS))
NATIVE_DIR = artifacts/transplant/$(NATIVE_VER)
# family array knob: family=wrapper,native,compressed (comma or space separated, order preserved)
comma := ,
FAMILY_LIST = $(strip $(subst $(comma), ,$(family)))
OUTPUT_ROOT := $(if $(ODIR),$(ODIR),$(CURDIR)/packing)
.PHONY: help all runtime stage deb pacman deb-native pacman-native native-pkg batch clean status steps matrix selfcheck release-upload test transplant transplant-predict transplant-check transplant-upx seccomp-harden family-wrapper family-native family-compressed deb-compressed pacman-compressed range-build fleet-upx fleet-status sha-stage push-stage clean-artifacts clean-version
help:
@echo "OpenCode Termux build helper"
@echo
@echo "━━━ [Families] Single-version per-family builds ━━━"
@echo " make family-wrapper VER=1.18.21 # wrapper (deb + pacman)"
@echo " make family-native VER=1.18.21 # transplant + seccomp-harden + native pkgs"
@echo " make family-compressed VER=1.18.21 # UPX-compressed variant (local)"
@echo
@echo "━━━ [Range Batch] Multi-version builds ━━━"
@echo " make range-build FROM=1.18.15 TO=1.18.27 LINES=wrapper,native"
@echo " Features: continue-on-fail, npm retry ≤3, disk guardrail, SHA256SUMS"
@echo
@echo "━━━ [Fleet] Distributed UPX ━━━"
@echo " python3 tools/fleet-push.py # 推荐: 节点常驻 release 自源(断点续传)"
@echo " make fleet-upx VER=1.18.21 NODES=\"miao@host1 miao@host2\" # 旧法: 本机分发"
@echo " make fleet-status # probe nodes"
@echo
@echo "━━━ [Release Staging] ━━━"
@echo " make sha-stage # regenerate SHA256SUMS-prebatch"
@echo " make push-stage TAG=Push260903 BATCH=prebatch # dry-run upload"
@echo " Set PUSH=1 to actually execute"
@echo
@echo "━━━ [Legacy] Per-target builds ━━━"
@echo " Glibc: make all VER=1.2.10 PKG=both | make runtime | make stage | make deb | make pacman"
@echo " Native: make transplant VER=1.18.21 | make deb-native | make pacman-native | make native-pkg"
@echo " Compressed: make transplant-upx VER=1.18.21"
@echo
@echo "━━━ [Batch/Matrix] ━━━"
@echo " make batch VERS='1.2.10 1.2.11' PKG=both"
@echo " make matrix VERS='1.2.9 1.2.10' TARGET_HOST=192.168.1.22 TARGET_USER=u0_a258"
@echo
@echo "━━━ [Debug/Introspection] ━━━"
@echo " make steps | make status | make selfcheck | make test"
@echo
@echo "━━━ [Housekeeping] ━━━"
@echo " make clean-version VER=<v> # 三清本版中间产物(NOT_CLEAN=1 阻止;--not-clean 等价物)"
@echo " ⚠ release-upload NATIVE=1 须带 NOT_CLEAN=1(自洁会移除 transplant 上传源)"
@echo " make clean-artifacts VER=1.18.21 # remove transplant artifacts"
@echo " make clean # remove wrapper staging"
@echo
@echo "━━━ [Transplant Notes] ━━━"
@echo " section-format graphs require android Bun base >= 1.4.0"
@echo " make transplant-predict VER=1.18.22 # dry-run feasibility gate"
@echo " QUARANTINE: bun-base resolve failure writes bun-base.QUARANTINE.json"
@echo
@echo "━━━ [Wrapper CLI] ━━━"
@echo " ./tools/make-opencode --all --ver 1.2.10 --pkg both"
@echo " TARGET_HOST=192.168.1.22 TARGET_USER=u0_a258 ./tools/upgrade-matrix.sh"
@echo
@echo "━━━ ⚠ Standalone (frozen) ━━━"
@echo " No new standalone builds. Use scripts directly:"
@echo " ./scripts/package/package_deb_standalone.sh"
@echo " ./scripts/package/package_pacman_standalone.sh"
steps:
@echo "Build steps: clean -> runtime -> stage -> package"
@echo "Package steps: deb and/or pacman depending on PKG"
@echo "Output root: $(OUTPUT_ROOT)"
@echo "Mix(flatten): $(MIX)"
all: clean runtime stage
@V="$(VER)"; \
if [ "$$V" = "latest" ]; then V=""; fi; \
if [ "$(PKG)" = "deb" ]; then \
$(MAKE) deb VERSION=$$V; \
elif [ "$(PKG)" = "pacman" ]; then \
$(MAKE) pacman VERSION=$$V; \
else \
$(MAKE) deb VERSION=$$V && $(MAKE) pacman VERSION=$$V; \
fi
batch-v2:
@if [ -z "$(VERS)" ]; then \
echo "Error: VERS is empty. Example: make batch-v2 VERS='2.0.[0-12]' PKG=both"; \
exit 1; \
fi
@expanded=(); \
for token in $(VERS); do \
if [[ "$$token" =~ ^([0-9]+\.[0-9]+)\.\[([0-9]+)-([0-9]+)\]$$ ]]; then \
base="$${BASH_REMATCH[1]}"; start="$${BASH_REMATCH[2]}"; end="$${BASH_REMATCH[3]}"; \
for ((i=start; i<=end; i++)); do expanded+=("$$base.$$i"); done; \
else \
expanded+=("$$token"); \
fi; \
done; \
for v in "$${expanded[@]}"; do \
echo "=== Batch v2 build for version $$v ==="; \
if [ "$(PKG)" = "both" ] || [ "$(PKG)" = "native" ]; then \
$(MAKE) family-v2-native VER=$$v NOT_CLEAN='$(NOT_CLEAN)' || exit 1; \
fi; \
if [ "$(PKG)" = "both" ] || [ "$(PKG)" = "compressed" ]; then \
$(MAKE) family-v2-compressed VER=$$v NOT_CLEAN='$(NOT_CLEAN)' || exit 1; \
fi; \
if [ "$(PKG)" = "both" ] || [ "$(PKG)" = "wrapper" ]; then \
$(MAKE) family-v2-wrapper VER=$$v NOT_CLEAN='$(NOT_CLEAN)' || exit 1; \
fi; \
if [ "$(NOT_CLEAN)" != "1" ]; then \
$(MAKE) --no-print-directory clean-version VER=$$v || exit 1; \
fi; \
done
# batch: NOT_CLEAN passthrough to sub-make is defensive — 'all' has no autoclean
# hook today; kept so a future hook inside 'all' honors the opt-out (momus NIT).
batch:
@if [ -z "$(VERS)" ]; then \
echo "Error: VERS is empty. Example: make batch VERS='1.2.10 1.2.11' PKG=both"; \
exit 1; \
fi
@expanded=(); \
for token in $(VERS); do \
if [[ "$$token" =~ ^([0-9]+\.[0-9]+)\.\[([0-9]+)-([0-9]+)\]$$ ]]; then \
base="$${BASH_REMATCH[1]}"; start="$${BASH_REMATCH[2]}"; end="$${BASH_REMATCH[3]}"; \
for ((i=start; i<=end; i++)); do expanded+=("$$base.$$i"); done; \
else \
expanded+=("$$token"); \
fi; \
done; \
for v in "$${expanded[@]}"; do \
echo "=== Batch build for version $$v ==="; \
$(MAKE) all VER=$$v PKG=$(PKG) MORE="$(MORE)" PACKAGER_NAME='$(PACKAGER_NAME)' ODIR='$(ODIR)' MIX='$(MIX)' NOT_CLEAN='$(NOT_CLEAN)' || exit 1; \
if [ "$(NOT_CLEAN)" != "1" ]; then \
$(MAKE) --no-print-directory clean-version VER=$$v || exit 1; \
fi; \
done
runtime:
@if [ "$(VER)" = "latest" ]; then \
./tools/produce-local.sh $(MORE); \
else \
./tools/produce-local.sh $(VER) $(MORE); \
fi
stage:
./scripts/build.sh
deb:
rm -rf packing/dpkg/work
MAINTAINER='$(PACKAGER_NAME)' ./scripts/package/package_deb.sh
@if [ "$(OUTPUT_ROOT)" != "$(CURDIR)/packing" ]; then \
if [ "$(MIX)" = "1" ]; then \
mkdir -p "$(OUTPUT_ROOT)" && cp -f packing/dpkg/opencode1-wrapper_$(VER)_aarch64.deb "$(OUTPUT_ROOT)/"; \
else \
mkdir -p "$(OUTPUT_ROOT)/deb" && cp -f packing/dpkg/opencode1-wrapper_$(VER)_aarch64.deb "$(OUTPUT_ROOT)/deb/"; \
fi; \
fi
pacman:
rm -rf packing/pacman/pkg packing/pacman/src
PACKAGER_NAME='$(PACKAGER_NAME)' ./scripts/package/package_pacman.sh
@# phase0 兜底: repack any relative-convention output to absolute members (idempotent skip on absolute input)
@for f in packing/pacman/opencode1-wrapper-$(VER)-*.pkg.*; do \
[ -f "$$f" ] || continue; \
bash tools/pkg-abs-repack.sh "$$f" || exit 1; \
done
@if [ "$(OUTPUT_ROOT)" != "$(CURDIR)/packing" ]; then \
if [ "$(MIX)" = "1" ]; then \
mkdir -p "$(OUTPUT_ROOT)" && cp -f packing/pacman/opencode1-wrapper-$(VER)-*.pkg.* "$(OUTPUT_ROOT)/"; \
else \
mkdir -p "$(OUTPUT_ROOT)/pacman" && cp -f packing/pacman/opencode1-wrapper-$(VER)-*.pkg.* "$(OUTPUT_ROOT)/pacman/"; \
fi; \
fi
# Native provider packaging (transplant revival line, stable mainline since 27/28).
# Provides the same `opencode` command as the wrapper packages; the two
# providers conflict (installing one replaces the other). The wrapper line is now the appendix (renamed opencode-wrapper); native is the stable mainline.
deb-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make deb-native VER=1.18.21"; \
exit 1; \
fi
rm -rf packing/dpkg-native/work
MAINTAINER='$(PACKAGER_NAME)' VERSION='$(VER)' HOOKS_ENABLE='$(HOOKS_ENABLE)' HOOKS_DISABLE='$(HOOKS_DISABLE)' HOOK_STALE_SERVE_KILL_VERSIONS='$(HOOK_STALE_SERVE_KILL_VERSIONS)' ./scripts/package/package_deb_native.sh
@if [ "$(OUTPUT_ROOT)" != "$(CURDIR)/packing" ]; then \
if [ "$(MIX)" = "1" ]; then \
mkdir -p "$(OUTPUT_ROOT)" && cp -f $$(case $(VER) in 1.*) echo opencode1;; *) echo opencode;; esac)_$(VER)_aarch64.deb "$(OUTPUT_ROOT)/"; \
else \
mkdir -p "$(OUTPUT_ROOT)/deb" && cp -f $$(case $(VER) in 1.*) echo opencode1;; *) echo opencode;; esac)_$(VER)_aarch64.deb "$(OUTPUT_ROOT)/deb/"; \
fi; \
fi
pacman-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make pacman-native VER=1.18.21"; \
exit 1; \
fi
rm -rf packing/pacman/pkg packing/pacman/src
PACKAGER_NAME='$(PACKAGER_NAME)' VERSION='$(VER)' HOOKS_ENABLE='$(HOOKS_ENABLE)' HOOKS_DISABLE='$(HOOKS_DISABLE)' HOOK_STALE_SERVE_KILL_VERSIONS='$(HOOK_STALE_SERVE_KILL_VERSIONS)' ./scripts/package/package_pacman_native.sh
@# phase0 兜底: repack any relative-convention output to absolute members (idempotent skip on absolute input)
@for f in packing/pacman/$$(case $(VER) in 1.*) echo opencode1;; *) echo opencode;; esac)-$(VER)-*.pkg.*; do \
[ -f "$$f" ] || continue; \
bash tools/pkg-abs-repack.sh "$$f" || exit 1; \
done
@if [ "$(OUTPUT_ROOT)" != "$(CURDIR)/packing" ]; then \
if [ "$(MIX)" = "1" ]; then \
mkdir -p "$(OUTPUT_ROOT)" && cp -f packing/pacman/$$(case $(VER) in 1.*) echo opencode1;; *) echo opencode;; esac)-$(VER)-*.pkg.* "$(OUTPUT_ROOT)/"; \
else \
mkdir -p "$(OUTPUT_ROOT)/pacman" && cp -f packing/pacman/$$(case $(VER) in 1.*) echo opencode1;; *) echo opencode;; esac)-$(VER)-*.pkg.* "$(OUTPUT_ROOT)/pacman/"; \
fi; \
fi
native-pkg: deb-native pacman-native
status:
@echo "Staged runtime:"; \
if [ -x artifacts/staged/prefix/lib/opencode/runtime/opencode ]; then \
artifacts/staged/prefix/lib/opencode/runtime/opencode --version; \
else \
echo "<missing>"; \
fi
selfcheck:
./tools/plugin-selfcheck.sh
# Run the shell unit test suite (bats). Fetches a pinned bats-core on demand.
test:
./tests/run.sh
matrix:
@VERS='$(VERS)' ODIR='$(ODIR)' TARGET_HOST='$(TARGET_HOST)' TARGET_PORT='$(TARGET_PORT)' TARGET_USER='$(TARGET_USER)' ./tools/upgrade-matrix.sh
# transplant: build native android binary via transplant pipeline
# (tools/transplant/transplant.py, zero-wrapper native-android path)
# Output: artifacts/transplant/<ver>/opencode-native + report.json
# bun-base pairing is resolved internally from tools/transplant/config/bun-bind.json
# (min_base_for_section: section-format graphs require android Bun base >= 1.4.0;
# trailer-format <=1.3.x require a 1.3.x base). A resolve failure aborts with a
# non-zero exit and writes artifacts/transplant/<ver>/bun-base.QUARANTINE.json
# (QUARANTINE) — no hardcoded base fallback remains in this chain.
transplant:
@if [ -z "$(VER)" ]; then \
echo "Error: VER is empty. Example: make transplant VER=1.3.13"; \
exit 1; \
fi
@printf '%s' '$(VER)' | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: invalid VER: $(VER)" >&2; exit 1; }
@echo "==> transplant VER=$(VER)"
@tgz="$${TMPDIR:-/tmp}/$$(npm pack '@opencode/cli-linux-arm64@$(VER)' --pack-destination "$${TMPDIR:-/tmp}" --silent | tail -n1)"; \
python3 tools/verify-release-input.py --kind opencode-cli --version '$(VER)' --file "$$tgz"; \
python3 tools/transplant/transplant.py all --ver '$(VER)' --tgz "$$tgz"
@# W7c2: TUI swap + post-swap pty probe are now performed INSIDE
@# `transplant.py all` (tools/transplant/swap_tui.py graft + tui_probe gate),
@# which records `tui_probe` in report.json. The Makefile only adds the
@# QUARANTINE signal when the bionic libopentui.so build is absent: upgrade
@# the old silent WARN into an explicit `tui:"absent"` in the report so the
@# gap is impossible to miss (no silent skip).
@if [ -f artifacts/transplant/opentui-bionic/libopentui.so ]; then \
echo "==> bionic libopentui present; swap + tui_probe handled by transplant.py all"; \
else \
echo "WARN: artifacts/transplant/opentui-bionic/libopentui.so not found -> quarantine (tui:absent)"; \
python3 -c "import json; p='artifacts/transplant/$(VER)/report.json'; \
r=json.load(open(p)); r['tui']='absent'; json.dump(r, open(p,'w'), indent=2); \
print(' report.tui=absent (bionic libopentui.so absent)')"; \
fi
@# W11: self-activating seccomp hardening (DT_NEEDED libopencode-crhandler.so
@# + SIGSYS handler/PLT interposer shim). WARN-skips when the toolchain is
@# missing; idempotent (already-hardened products are detected and skipped).
$(MAKE) --no-print-directory seccomp-harden VER=$(VER)
# libopentui (task-tui-common-fix): ensure the canonical bionic libopentui.so
# exists AND carries the FFI negative-coordinate guard. Every transplant-built
# version equal-length-swaps THIS file, so a stale/unguarded slot lib poisons
# the whole version family (the 1.18.15-27 batch shipped pre-342d68d guards).
# Common layer: apply ALL patches/opentui/*.patch -> zig bionic build ->
# symbol-range guard scan + dlopen hostile-FFI harness -> install to slot.
# LIBOPENTUI_OPTIONAL=1 downgrades a build failure to a WARN (quarantine path
# for toolchain-less environments); default is loud.
libopentui:
@if [ -f artifacts/transplant/opentui-bionic/libopentui.so ] && \
bash tools/transplant/build-libopentui.sh --check artifacts/transplant/opentui-bionic/libopentui.so; then \
echo "==> libopentui: guard-verified .so present"; \
elif bash tools/transplant/build-libopentui.sh; then \
echo "==> libopentui: built + guard-verified via common layer"; \
elif [ "$(LIBOPENTUI_OPTIONAL)" = "1" ]; then \
echo "WARN: libopentui build failed -> transplant will quarantine (tui:absent)"; \
else \
echo "ERROR: libopentui build failed (common layer); set LIBOPENTUI_OPTIONAL=1 to downgrade to WARN" >&2; \
exit 1; \
fi
transplant: VER?= latest
# transplant-predict: DRY-RUN feasibility gate (no downloads; metadata/local state only).
# Wraps `transplant.py predict`; exit 0=OK, 1=FAIL, 2=NEEDS_INFO. Safe to run before `transplant`.
transplant-predict:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make transplant-predict VER=1.18.22"; \
exit 1; \
fi
@echo "==> transplant-predict VER=$(VER)"
python3 tools/transplant/transplant.py predict --ver $(VER)
transplant: libopentui
transplant-predict: VER?= latest
# transplant-upx: OPTIONAL final step — UPX-pack an already-revived native product.
# MUST run AFTER transplant (swap_tui.py / revive_patch break on a packed ELF).
# Requires a revived product in $(NATIVE_DIR); override source with SRC=<path>.
# Produces opencode-native-<ver>-upx, KEEPS the original, writes upx-report-<ver>.json
# (both sha256 + ratio). WARN-skips when upx is missing.
transplant-upx:
@if [ -z "$(VER)" ]; then \
echo "Error: VER is empty. Example: make transplant-upx VER=1.18.21"; \
exit 1; \
fi
@if ! command -v upx >/dev/null 2>&1; then \
echo "WARN: upx not found; skipping UPX compression (transplant-upx skipped)"; \
exit 0; \
fi
@SRC="$(SRC)"; \
if [ -z "$$SRC" ]; then \
if [ -f $(NATIVE_DIR)/opencode-native-tui ]; then SRC=$(NATIVE_DIR)/opencode-native-tui; \
elif [ -f $(NATIVE_DIR)/opencode-native-revived ]; then SRC=$(NATIVE_DIR)/opencode-native-revived; \
elif [ -f $(NATIVE_DIR)/opencode-native ]; then SRC=$(NATIVE_DIR)/opencode-native; \
else \
echo "Error: no revived product in $(NATIVE_DIR); run 'make transplant VER=$(VER)' first"; \
exit 1; \
fi; \
fi; \
echo "==> transplant-upx VER=$(VER) source=$$SRC"; \
OUT=$(NATIVE_DIR)/opencode-native-$(VER)-upx; \
cp -p "$$SRC" "$$OUT"; \
upx $(UPX_OPTS) --no-color "$$OUT"; \
RC=$$?; \
if [ $$RC -ne 0 ]; then echo "Error: upx failed rc=$$RC"; rm -f "$$OUT"; exit $$RC; fi; \
SRC_SHA=$$(sha256sum "$$SRC" | cut -d' ' -f1); \
OUT_SHA=$$(sha256sum "$$OUT" | cut -d' ' -f1); \
ORIG_SIZE=$$(stat -c%s "$$SRC"); \
UPX_SIZE=$$(stat -c%s "$$OUT"); \
RATIO=$$(awk "BEGIN{printf \"%.2f\", $$UPX_SIZE*100/$$ORIG_SIZE}"); \
echo "original : $$SRC_SHA $$ORIG_SIZE B"; \
echo "upx : $$OUT_SHA $$UPX_SIZE B ($$RATIO%)"; \
python3 -c 'import json,sys; src,out,ss,os_,osz,usz,ratio=sys.argv[1:]; ver=out.split("opencode-native-",1)[1].split("-upx",1)[0]; d=out.rfind("/"); rep={"version":ver,"source":src,"source_sha256":ss,"source_size":int(osz),"upx_binary":out,"upx_sha256":os_,"upx_size":int(usz),"ratio_pct":float(ratio),"note":"packed MUST be the final pipeline step; swap_tui.py/revive_patch break on packed ELF"}; open(out[:d]+"/upx-report-"+ver+".json","w").write(json.dumps(rep,indent=2)); print("wrote upx-report-"+ver+".json")' "$$SRC" "$$OUT" "$$SRC_SHA" "$$OUT_SHA" "$$ORIG_SIZE" "$$UPX_SIZE" "$$RATIO" || exit 1; \
echo "==> transplant-upx done: original kept at $$SRC; upx at $$OUT"
transplant-upx: VER?= latest
transplant-upx: UPX_OPTS?= --best
# seccomp-harden: W11 self-activating seccomp hardening of a revived native product.
# Builds tools/shim/sigsys_handler.c -> libopencode-crhandler.so (SIGSYS handler
# for the inline-svc sites + syscall()/close_range() PLT interposers for the
# spawn-child fd-hygiene path) and rewrites the product's dynamic section so the
# shim is the FIRST DT_NEEDED entry (no env vars, no LD_PRELOAD). The patcher is
# zero-displacement (tools/transplant/crhandler_patch.py): file size, PT_LOADs
# and the graft are provably untouched. The PRE-patch copy is kept as
# *.pre-crhandler next to the product for rollback evidence. WARN-skips when the
# toolchain is missing; idempotent (already-hardened products are detected).
# Runs automatically inside `transplant` after the TUI swap; MUST run BEFORE
# transplant-upx (upx packing hides the dynamic section).
seccomp-harden:
@if [ -z "$(VER)" ]; then \
echo "Error: VER is empty. Example: make seccomp-harden VER=1.18.21"; \
exit 1; \
fi
@if ! command -v clang >/dev/null 2>&1; then \
echo "WARN: clang not found; skipping seccomp hardening (seccomp-harden skipped)"; \
exit 0; \
fi
@if [ ! -f tools/shim/sigsys_handler.c ] || [ ! -f tools/transplant/crhandler_patch.py ]; then \
echo "WARN: tools/shim/sigsys_handler.c or tools/transplant/crhandler_patch.py missing; skipping seccomp hardening"; \
exit 0; \
fi
@SRC="$(SRC)"; \
if [ -z "$$SRC" ]; then \
if [ -f $(NATIVE_DIR)/opencode-native-tui ]; then SRC=$(NATIVE_DIR)/opencode-native-tui; \
elif [ -f $(NATIVE_DIR)/opencode-native-revived ]; then SRC=$(NATIVE_DIR)/opencode-native-revived; \
elif [ -f $(NATIVE_DIR)/opencode-native ]; then SRC=$(NATIVE_DIR)/opencode-native; \
else \
echo "WARN: no revived product in $(NATIVE_DIR); skipping seccomp hardening"; \
exit 0; \
fi; \
fi; \
echo "==> seccomp-harden VER=$(VER) source=$$SRC"; \
clang -shared -fPIC -O2 -o $(NATIVE_DIR)/libopencode-crhandler.so tools/shim/sigsys_handler.c || exit 1; \
if grep -aqF libopencode-crhandler.so "$$SRC"; then \
echo "==> seccomp-harden: $$SRC already hardened, skip (shim rebuilt)"; \
exit 0; \
fi; \
cp -p "$$SRC" "$$SRC.pre-crhandler" || exit 1; \
python3 tools/transplant/crhandler_patch.py "$$SRC" || exit 1; \
echo "seccomp-harden: pre-patch copy kept at $$SRC.pre-crhandler";
@# Post-patch sync: when tui was patched, ensure revived reflects the
@# hardened binary. transplant.py produces both tui and revived, but
@# seccomp-harden patches only the preferred source (typically tui).
if [ "$$SRC" = "$(NATIVE_DIR)/opencode-native-tui" ] && [ -f "$(NATIVE_DIR)/opencode-native-revived" ]; then \
if ! grep -aqF libopencode-crhandler.so "$(NATIVE_DIR)/opencode-native-revived"; then \
rm -f "$(NATIVE_DIR)/opencode-native-revived"; \
cp -p "$(NATIVE_DIR)/opencode-native-tui" "$(NATIVE_DIR)/opencode-native-revived"; \
echo "seccomp-harden: synced hardened tui -> revived"; \
fi; \
fi
# transplant-check: golden regression across layout families
# (tests/transplant/test_golden.py; fixtures pre-downloaded by scripts/fetch-fixtures.sh)
transplant-check:
python3 tests/transplant/test_golden.py --fixtures-dir tests/transplant/fixtures/tgz
# ══════════════════════════════════════════════════════════════════════
# Grouped family targets (single-version per-family builds)
# ══════════════════════════════════════════════════════════════════════
# family-wrapper: build wrapper packages for a single version
# Usage: make family-wrapper VER=1.18.21
family-wrapper: runtime stage
@if [ "$(PKG)" = "deb" ]; then \
$(MAKE) deb VER=$(VER); \
elif [ "$(PKG)" = "pacman" ]; then \
$(MAKE) pacman VER=$(VER); \
else \
$(MAKE) deb VER=$(VER) && $(MAKE) pacman VER=$(VER); \
fi
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi
# family-native: transplant + seccomp-harden + native packages for a single version
# Usage: make family-native VER=1.18.21
family-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family-native VER=1.18.21"; \
exit 1; \
fi
$(MAKE) transplant VER=$(VER)
$(MAKE) deb-native VER=$(VER) && $(MAKE) pacman-native VER=$(VER)
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi
# family-compressed: UPX-compressed variant (local build)
# Usage: make family-compressed VER=1.18.21
family-compressed:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family-compressed VER=1.18.21"; \
exit 1; \
fi
$(MAKE) transplant VER=$(VER)
$(MAKE) transplant-upx VER=$(VER)
$(MAKE) deb-compressed VER=$(VER) && $(MAKE) pacman-compressed VER=$(VER)
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi
# family: array dispatcher over the three family chains (FEATURE: family as ARRAY)
# Usage: make family=wrapper,native,compressed VER=1.18.21 (comma or space separated)
# Order is preserved as given; canonical order is wrapper -> native -> compressed.
.PHONY: family
family:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family=wrapper,native VER=1.18.21"; \
exit 1; \
fi
@if [ -z "$(FAMILY_LIST)" ]; then \
echo "Error: family is empty. Valid entries: wrapper, native, compressed (comma or space separated)."; \
exit 1; \
fi
@invalid=""; \
for f in $(FAMILY_LIST); do \
case "$$f" in wrapper|native|compressed) ;; *) invalid="$$invalid $$f" ;; esac; \
done; \
if [ -n "$$invalid" ]; then \
echo "Error: unknown family:$$invalid (valid: wrapper, native, compressed)"; \
exit 1; \
fi
@for f in $(FAMILY_LIST); do \
echo "==> family chain: $$f (VER=$(VER))"; \
$(MAKE) --no-print-directory family-$$f VER=$(VER) || exit 1; \
done
@echo "==> family chains complete: [$(FAMILY_LIST)] VER=$(VER)"
# maintain-upload: maintainer fleet upload via tools/maintain.sh
# Usage: make maintain-upload TAG=Push260906 [FAMILY=compressed] [ATTEMPTS=3] [DRY=1] [VERSIONS=V1,V2] [NODES=n1,n2]
.PHONY: maintain-upload
maintain-upload:
@if [ -z "$(strip $(TAG))" ] || [ "$(strip $(TAG))" = "$(TAG)" ] && [ -z "$(origin TAG)" ]; then \
echo "Error: TAG is required. Example: make maintain-upload TAG=Push260906 FAMILY=compressed"; \
exit 1; \
fi
bash tools/maintain.sh --upload --tag "$(TAG)" \
$(if $(FAMILY),--family "$(FAMILY)") \
$(if $(VERSIONS),--versions "$(VERSIONS)") \
$(if $(ATTEMPTS),--attempts $(ATTEMPTS)) \
$(if $(NODES),--nodes "$(NODES)") \
$(if $(SOURCE),--source $(SOURCE)) \
$(if $(DRY),--dry-run) \
$(if $(NO_UPLOAD),--no-upload)
# sync-db: refresh unified hope2333.db.tar.gz on the 5 release CDNs
# Usage: make sync-db [DRY=1] (also: tools/maintain.sh --sync-db)
.PHONY: sync-db
sync-db:
bash tools/maintain.sh --sync-db $(if $(DRY),--dry-run)
# clear: maintainer cache-layer cleanup (mutually exclusive with build surfaces)
# Usage: make clear -> statistics only (non-destructive)
# make clear LAYERS=layer1,layer2 CONFIRM=1
# make clear LAYERS=npm-cacache,npx-cache CONFIRM=1 (npm/npx download-chain caches)
# Post-upload one-shot: tools/maintain.sh --upload ... --auto-clean
.PHONY: clear
clear:
@if [ "$(words $(filter-out clear,$(MAKECMDGOALS)))" -gt 0 ]; then \
echo "Error: clear is mutually exclusive with other targets: $(filter-out clear,$(MAKECMDGOALS))"; \
exit 1; \
fi
@if [ -n "$(strip $(VERS))" ]; then \
echo "Error: clear is mutually exclusive with VERS=$(VERS)"; \
exit 1; \
fi
@if [ -n "$(strip $(BATCH))" ]; then \
echo "Error: clear is mutually exclusive with BATCH=$(BATCH)"; \
exit 1; \
fi
@if [ -n "$(strip $(PUSH))" ]; then \
echo "Error: clear is mutually exclusive with PUSH=$(PUSH)"; \
exit 1; \
fi
@if [ -n "$(strip $(family))" ]; then \
echo "Error: clear is mutually exclusive with family=$(family)"; \
exit 1; \
fi
@if [ "$(strip $(VER))" != "latest" ] && [ "$(strip $(VER))" != "" ]; then \
echo "Error: clear is mutually exclusive with VER=$(VER)"; \
exit 1; \
fi
bash tools/maintain.sh --clear LAYERS="$(LAYERS)" $(if $(CONFIRM),CONFIRM=1) $(if $(DRY),DRY=1)
# ══════════════════════════════════════════════════════════════════════
deb-compressed:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make deb-compressed VER=1.18.21"; \
exit 1; \
fi
@bin="$${OPENCODE_COMPRESSED_BIN:-$(NATIVE_DIR)/opencode-native-$(VER)-upx}"; \
shim="$${OPENCODE_CRHANDLER_SO:-$(NATIVE_DIR)/libopencode-crhandler.so}"; \
VERSION=$(VER) OPENCODE_COMPRESSED_BIN="$$bin" OPENCODE_CRHANDLER_SO="$$shim" bash scripts/package/package_deb_compressed.sh
pacman-compressed:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make pacman-compressed VER=1.18.21"; \
exit 1; \
fi
@bin="$${OPENCODE_COMPRESSED_BIN:-$(NATIVE_DIR)/opencode-native-$(VER)-upx}"; \
shim="$${OPENCODE_CRHANDLER_SO:-$(NATIVE_DIR)/libopencode-crhandler.so}"; \
VERSION=$(VER) OPENCODE_COMPRESSED_BIN="$$bin" OPENCODE_CRHANDLER_SO="$$shim" OCOMP_SHIMS='$(OCOMP_SHIMS)' PKGREL='$(PKGREL)' bash scripts/package/package_pacman_compressed.sh
@# phase0 兜底: repack a relative-convention output to absolute members
@# (idempotent skip on absolute input). Scoped to the EXACT pkgver just
@# built — a broad *-compressed-$(VER)-* glob also matches every historical
@# package in packing/pacman/, and pkg-abs-repack.sh bumps pkgrel + deletes
@# its input, so a wide glob would silently renumber the whole shelf.
@for f in packing/pacman/*-$(VER)-$(if $(PKGREL),$(PKGREL),1)-*.pkg.*; do \
[ -f "$$f" ] || continue; \
bash tools/pkg-abs-repack.sh "$$f" || exit 1; \
done
# Range batch build (multi-version, continue-on-fail)
# ══════════════════════════════════════════════════════════════════════
# range-build: build multiple versions across families with resilience
# Usage: make range-build FROM=1.18.15 TO=1.18.27 LINES=wrapper,native
# Features: continue-on-fail, npm retry ≤3, disk guardrail, SHA256SUMS accumulation
range-build:
@if [ -z "$(FROM)" ] || [ -z "$(TO)" ]; then \
echo "Error: FROM and TO are required. Example: make range-build FROM=1.18.15 TO=1.18.27 LINES=wrapper,native"; \
exit 1; \
fi
@if [ -z "$(LINES)" ]; then \
echo "Error: LINES is required (wrapper,native[,compressed])"; \
exit 1; \
fi
bash scripts/range-build.sh FROM=$(FROM) TO=$(TO) LINES=$(LINES)
# ══════════════════════════════════════════════════════════════════════
# Fleet UPX (distributed compression)
# ══════════════════════════════════════════════════════════════════════
# fleet-upx: distribute UPX compression across multiple nodes
# Usage: make fleet-upx VER=1.18.21 NODES="miao@100.98.3.121 miao@100.110.50.37"
# Features: NODE_PASSWORD env / -p, sshpass -e, xz -9 outbound, opportunistic dispatch, local verify
fleet-upx:
@if [ -z "$(VER)" ] || [ -z "$(NODES)" ]; then \
echo "Error: VER and NODES are required. Example: make fleet-upx VER=1.18.21 NODES=\"miao@host1 miao@host2\""; \
exit 1; \
fi
bash scripts/fleet-upx.sh VER=$(VER) NODES="$(NODES)"
# fleet-status: probe fleet nodes for availability
fleet-status:
bash scripts/fleet-upx.sh --status
# ══════════════════════════════════════════════════════════════════════
# Release staging
# ══════════════════════════════════════════════════════════════════════
# sha-stage: regenerate SHA256SUMS-prebatch.txt from packing/
sha-stage:
bash scripts/sha-stage.sh
# push-stage: build gh release create command (dry-run by default)
# Usage: make push-stage TAG=Push260903 BATCH=prebatch
# Set PUSH=1 to actually execute upload
push-stage:
@if [ -z "$(TAG)" ]; then \
echo "Error: TAG is required. Example: make push-stage TAG=Push260903 BATCH=prebatch"; \
exit 1; \
fi
bash scripts/push-stage.sh TAG=$(TAG) BATCH=$(BATCH)$(if $(PUSH),PUSH=1)
# ══════════════════════════════════════════════════════════════════════
# Housekeeping
# ══════════════════════════════════════════════════════════════════════
# clean-artifacts: remove transplant artifacts for a specific version
# Usage: make clean-artifacts VER=1.18.21
clean-artifacts:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make clean-artifacts VER=1.18.21"; \
exit 1; \
fi
rm -rf artifacts/transplant/$(VER)
@echo "Cleaned artifacts for $(VER)"
# clean-version: autoclean per-version intermediates after a build (four-way clean).
# 1. $(TMPDIR)/v2src/opencode-<ver> unpack tree (tarball *.tgz point-kept)
# 2. artifacts/staged + packing/{dpkg,dpkg-native,dpkg-compressed}/work + packing/pacman/{pkg,src}
# 3. artifacts/transplant/<ver>
# 4. artifacts/wrapper/<ver>
# Kept: artifacts/build/<ver> (bin + sha + json), packing delivery packages (*<ver>*),
# $(TMPDIR)/v2src/*.tgz, artifacts/transplant/android-bun, artifacts/wrapper/glibc-standalone.
# Opt out: NOT_CLEAN=1 (make has no --not-clean long option).
# ⚠ release-upload NATIVE=1 uploads from artifacts/transplant/<ver> — run it with NOT_CLEAN=1.
# Usage: make clean-version VER=2.0.12
clean-version:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make clean-version VER=2.0.12"; \
exit 1; \
fi
@ver="$(VER)"; \
if [ -z "$$ver" ] || [ "$$ver" = "." ] || [ "$$ver" = ".." ] || [ "$${ver%/}" != "$$ver" ]; then \
echo "Error: invalid VER: [$$ver]"; \
exit 1; \
fi
@tmpdir="$${TMPDIR:-/data/data/com.termux/files/usr/tmp}"; \
ver="$(VER)"; \
fail=""; \
if [ "$(NOT_CLEAN)" = "1" ]; then \
echo "==> clean-version: NOT_CLEAN=1, skipping autoclean for $$ver"; \
elif [ -d "artifacts/build/$$ver" ] && [ ! -f "artifacts/build/$$ver/opencode-native-revived" ]; then \
echo "Error: bin missing, refuse to clean: artifacts/build/$$ver/opencode-native-revived"; \
exit 1; \
else \
echo "==> clean-version $$ver: removing v2src tree, staging/work dirs, transplant/$$ver, wrapper/$$ver"; \
rm -rf "$$tmpdir/v2src/opencode-$$ver" \
artifacts/staged \
packing/dpkg/work packing/dpkg-native/work packing/dpkg-compressed/work \
packing/pacman/pkg packing/pacman/src \
"artifacts/transplant/$$ver" \
"artifacts/wrapper/$$ver"; \
if [ -d "artifacts/build/$$ver" ]; then \
[ -f "artifacts/build/$$ver/opencode-native-revived" ] || fail="$$fail opencode-native-revived"; \
[ -f "artifacts/build/$$ver/build.sha256" ] || fail="$$fail build.sha256"; \
fi; \
[ ! -d "artifacts/transplant" ] || [ -d "artifacts/transplant/android-bun" ] || fail="$$fail android-bun"; \
[ ! -d "artifacts/wrapper" ] || [ -d "artifacts/wrapper/glibc-standalone" ] || fail="$$fail glibc-standalone"; \
if [ -n "$$fail" ]; then echo "Error: kept files missing after clean:$$fail"; exit 1; fi; \
echo "==> kept: build/$$ver bin+sha+json, v2src tarballs, transplant/android-bun, wrapper/glibc-standalone, packing delivery packages"; \
echo "CLEAN_OK"; \
fi
# ══════════════════════════════════════════════════════════════════════
# Standalone (frozen, warning banner)
# ══════════════════════════════════════════════════════════════════════
# Standalone targets are FROZEN. No new builds. Use scripts directly:
# ./scripts/package/package_deb_standalone.sh
# ./scripts/package/package_pacman_standalone.sh
# ══════════════════════════════════════════════════════════════════════
clean:
rm -rf artifacts/staged packing/dpkg/work packing/pacman/pkg packing/pacman/src
@echo "Clean complete"
# NOTE: release-upload will gain a transplant prerequisite (native-android
# binary per version) — actual ordering wired in todo 18.
# ── Release upload (not shown in help) ──────────────────────────────────
# Automates: batch build → upload all assets to existing or new release tag.
# Usage:
# make release-upload TAG=Push260522 VERS='1.15.[1-7]'
# make release-upload TAG=Push260522 VERS='1.15.[1-7]' PKG=deb
# make release-upload VERS='1.2.[10-20]' REPO=Hope2333/opencode-termux
# make release-upload TAG=Push260901 NATIVE=1 VER=1.3.13 # + native assets
# # (raw binary + report + watcher + opencode deb/pacman providers)
# make release-upload TAG=Stable260901 NATIVE=STABLE VER=1.18.21
# # native assets as FORMAL release (no --prerelease flag;
# # stable-mainline notes per 27/28 switch)
#
# ⚠ NOT_CLEAN=1 wiring note: batch / batch-v2 / family-* targets autoclean
# per-version intermediates (make clean-version) after each version. release-upload
# NATIVE=1 uploads from artifacts/transplant/<ver> — run it with NOT_CLEAN=1
# (e.g. right after a NOT_CLEAN=1 batch) so the upload source is not removed first.
#
# Defaults:
# TAG = Push<YYMMDD> (auto-generated)
# VERS = (required for wrapper batch; may be omitted when NATIVE=1 + VER)
# PKG = both
# REPO = Hope2333/opencode-termux
# NATIVE = (empty) — set to 1 to also upload native-android transplant
# assets (opencode-native + report.json + watcher pkg) for a
# single version (VER, or VERS when it is a single version).
# Fails fast (exit 1) when artifacts/transplant/<ver> is missing
# or empty (anti-empty-release).
# NATIVE=STABLE uploads the same asset set as a FORMAL release
# (gh release create without --prerelease, stable-mainline notes)
# per the 27/28 mainline switch; NATIVE=1 keeps the legacy path.
release-upload:
@if [ -z "$(VERS)" ] && [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VERS is required. Example: make release-upload VERS='1.15.[1-7]' TAG=Push260522"; \
exit 1; \
fi
@if [ "$(NATIVE)" = "1" ] || [ "$(NATIVE)" = "STABLE" ]; then \
case "$(NATIVE_VER)" in 1.*) np=opencode1;; *) np=opencode;; esac; \
if [ ! -f "$(NATIVE_DIR)/opencode-native-revived" ] || [ -z "$$(ls -A $(NATIVE_DIR) 2>/dev/null)" ]; then \
echo "Error: NATIVE=$(NATIVE) but $(NATIVE_DIR) is missing or empty (anti-empty-release)" >&2; \
exit 1; \
fi; \
echo "NATIVE assets found: $(NATIVE_DIR)/opencode-native-revived + report.json + watcher pkg"; \
fi
@echo "=== Release upload: TAG=$(TAG) VERS=$(VERS) VER=$(VER) PKG=$(PKG) REPO=$(REPO) NATIVE=$(NATIVE) ==="
@if [ -n "$(VERS)" ]; then \
$(MAKE) batch VERS='$(VERS)' PKG='$(PKG)' ODIR='$(RELEASE_DIR)' MIX=1; \
fi
@echo "=== Uploading to release $(TAG) ==="; \
upload_failed=0; \
if ! gh release view "$(TAG)" --repo "$(REPO)" >/dev/null 2>&1; then \
echo "Creating release $(TAG)..."; \
if [ "$(NATIVE)" = "STABLE" ]; then \
case "$(NATIVE_VER)" in 1.*) np=opencode1;; *) np=opencode;; esac; \
gh release create "$(TAG)" --repo "$(REPO)" --title "$(TAG)" --notes "OpenCode for Termux. Mainline (stable since 27/28): native bionic line - opencode-<ver>-aarch64-android-native / opencode_<ver>_aarch64.deb / opencode-<ver>-*-aarch64.pkg.* - zero-wrapper, full TUI, Android API>=28. Appendix (legacy): wrapper packages opencode1-wrapper_<ver>_aarch64.deb / opencode1-wrapper-<ver>-aarch64.pkg.tar.*." 2>&1 || exit 1; \
else \
gh release create "$(TAG)" --repo "$(REPO)" --title "$(TAG)" --notes "Dual-track OpenCode for Termux. Track 1 (wrapper appendix, renamed opencode-wrapper): wrapper packages opencode1-wrapper_<ver>_aarch64.deb / opencode1-wrapper-<ver>-aarch64.pkg.tar.* - full TUI. Track 2 (native): v1=opencode1_<ver>_aarch64.deb / opencode1-<ver>-*-aarch64.pkg.* ; v2=opencode_* / opencode-<ver>-* / *-android-native assets - zero-wrapper, full TUI (bionic libopentui.so, W10a 5/5), Android API>=28." 2>&1 || exit 1; \
fi; \
else \
echo "Release $(TAG) exists; rebinding tag to HEAD via gh api (HTTPS, SSH 22 blocked)..."; \
gh api -X PATCH "repos/$(REPO)/git/refs/tags/$(TAG)" -f sha="$$(git rev-parse HEAD)" >/dev/null 2>&1 || echo " (tag rebind skipped: API refused or already current)"; \
fi; \
for f in $(RELEASE_DIR)/opencode1-wrapper_*.deb $(RELEASE_DIR)/opencode1-wrapper-*.pkg.*; do \
if [ -f "$$f" ]; then \
echo " uploading $$(basename $$f)..."; \
if ! gh release upload "$(TAG)" "$$f" --repo "$(REPO)" --clobber 2>&1; then upload_failed=1; fi; \
fi; \
done; \
mkdir -p "$(RELEASE_DIR)"; \
echo "--- Dual-track asset naming ---"; \
echo " wrapper line (appendix, renamed opencode1-wrapper): opencode1-wrapper_<ver>_aarch64.deb / opencode1-wrapper-<ver>-aarch64.pkg.tar.*"; \
echo " native line: v1=opencode1-<ver>* / v2=opencode-<ver>* (pkg+native-ELF+report+watcher); UPX asset stays opencode-native-<ver>-upx.xz"; \
if [ "$(NATIVE)" = "1" ] || [ "$(NATIVE)" = "STABLE" ]; then \
case "$(NATIVE_VER)" in 1.*) np=opencode1;; *) np=opencode;; esac; \
cp "$(NATIVE_DIR)/opencode-native-revived" "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-aarch64-android-native"; \
cp "$(NATIVE_DIR)/report.json" "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-report.json"; \
tar czf "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-watcher.tar.gz" -C tools/watcher watcher shim.js install.sh; \
echo "=== Building native provider packages (opencode) ==="; \
MAINTAINER='$(PACKAGER_NAME)' VERSION='$(NATIVE_VER)' ./scripts/package/package_deb_native.sh; \
PACKAGER_NAME='$(PACKAGER_NAME)' VERSION='$(NATIVE_VER)' ./scripts/package/package_pacman_native.sh; \
for f in packing/pacman/$$np-$(NATIVE_VER)-*.pkg.*; do [ -f "$$f" ] || continue; bash tools/pkg-abs-repack.sh "$$f" || exit 1; done; \
case "$(NATIVE_VER)" in 1.*) np=opencode1;; *) np=opencode;; esac; cp packing/dpkg-native/$$np_$(NATIVE_VER)_aarch64.deb "$(RELEASE_DIR)/" || exit 1; \
cp packing/pacman/$$np-$(NATIVE_VER)-*.pkg.* "$(RELEASE_DIR)/" || exit 1; \
for f in "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-aarch64-android-native" "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-report.json" "$(RELEASE_DIR)/$$np-$(NATIVE_VER)-watcher.tar.gz" $(RELEASE_DIR)/opencode_[0-9]*.deb $(RELEASE_DIR)/opencode-[0-9]*.pkg.*; do \
echo " uploading $$(basename $$f)..."; \
if ! gh release upload "$(TAG)" "$$f" --repo "$(REPO)" --clobber 2>&1; then upload_failed=1; fi; \
done; \
fi; \
if [ "$$upload_failed" -ne 0 ]; then echo "Error: one or more release assets failed to upload" >&2; exit 1; fi; \
echo "=== Done: https://github.com/$(REPO)/releases/tag/$(TAG) ==="
# ══════════════════════════════════════════════════════════════════════
# V2 (opencode 2.0.0 GA) — three-line build surface
#
# Identity: v2 replaces the v1 mainline (package name `opencode`). v1.18.31 is
# pinned as the final v1 release. Future v1 maintenance (if any) moves to the
# `opencode1*` package name, mutually exclusive with v2.
#
# Line TUI Channel make entry
# ────────────────────────────────────────────────────────────────────
# B native (default) ✅ android-bun source compile family-v2-native
# B compressed ✅ + UPX (standalone scheme) family-v2-compressed
# Wrapper ✅ bun-termux-loader wrap family-v2-wrapper
# A+ reserve headless ⚠️ wrapper compile -> transplant family-v2-reserve
# (non-special use only; TUI unavailable — documented upstream bun bug)
#
# Packaging reuses the v1 provider scripts verbatim by overriding
# TRANSPLANT_ROOT=artifacts/build (B line normalizes products to the
# opencode-native-revived / -upx contract names).
# ══════════════════════════════════════════════════════════════════════
# B-line source roots (env overridable)
V2_SRC ?=
V2_BUILD_ROOT ?= artifacts/build
V2_WRAP_ROOT ?= artifacts/wrapper
V2_WRAPPER_INPUT ?=
V2_GLIBC_TGZ ?= /data/data/com.termux/files/usr/tmp
V2_LOADER_ROOT ?= $(shell if [ -d $(HOME)/bun-termux-loader ]; then echo $(HOME)/bun-termux-loader; else echo $(HOME)/.local/share/bun-termux-loader; fi)
# build-native: B-line compile (android bun) -> artifacts/build/<ver>/opencode-native-revived
# Usage: make build-native VER=2.0.0 [V2_SRC=...] [OPENTUI_REBUILD=1]
.PHONY: build-native
build-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make build-native VER=2.0.0"; \
exit 1; \
fi
@printf '%s' '$(VER)' | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: invalid VER: $(VER)" >&2; exit 1; }
VER='$(VER)' V2_SRC='$(V2_SRC)' bash scripts/build-bionic.sh
# build-native-upx: UPX-compress the B-line product (optional final step)
.PHONY: build-native-upx
build-native-upx:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make build-native-upx VER=2.0.0"; \
exit 1; \
fi
src="artifacts/build/$(VER)/opencode-native-revived"; \
if [ ! -f "$$src" ]; then echo "Error: $$src missing; run 'make build-native VER=$(VER)' first"; exit 1; fi; \
cp -p "$$src" "artifacts/build/$(VER)/opencode-native-revived-upx"; \
$(UPX_BIN) $(UPX_OPTS) --no-color "artifacts/build/$(VER)/opencode-native-revived-upx"; \
sha256sum "artifacts/build/$(VER)/opencode-native-revived-upx" | awk '{print $$1}' > "artifacts/build/$(VER)/build-upx.sha256"; \
echo "==> upx: artifacts/build/$(VER)/opencode-native-revived-upx ($$(stat -c%s artifacts/build/$(VER)/opencode-native-revived-upx) B)"
build-native-upx: UPX_BIN?=upx
build-native-upx: UPX_OPTS?=-4
# family-v2-native: B-line compile + native deb/pacman (v1 provider scripts)
# Usage: make family-v2-native VER=2.0.0
.PHONY: family-v2-native
family-v2-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family-v2-native VER=2.0.0"; \
exit 1; \
fi
$(MAKE) --no-print-directory build-native VER=$(VER) V2_SRC='$(V2_SRC)'
command -v clang >/dev/null 2>&1 || { echo "FATAL: clang required — v2 native must ship the seccomp shim (issue #17)"; exit 1; }
$(MAKE) --no-print-directory harden-native VER=$(VER)
TRANSPLANT_ROOT=$(CURDIR)/artifacts/build $(MAKE) --no-print-directory deb-native VER=$(VER)
TRANSPLANT_ROOT=$(CURDIR)/artifacts/build $(MAKE) --no-print-directory pacman-native VER=$(VER)
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi
# family-v2-compressed: B-line + UPX + compressed deb/pacman (v1 standalone scheme)
# Usage: make family-v2-compressed VER=2.0.0
.PHONY: family-v2-compressed
family-v2-compressed: UPX_BIN?=upx
family-v2-compressed:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family-v2-compressed VER=2.0.0"; \
exit 1; \
fi
$(MAKE) --no-print-directory build-native VER=$(VER) V2_SRC='$(V2_SRC)'
$(MAKE) --no-print-directory harden-native VER=$(VER)
UPX_BIN='$(UPX_BIN)' $(MAKE) --no-print-directory build-native-upx VER=$(VER)
TRANSPLANT_ROOT=$(CURDIR)/artifacts/build OPENCODE_COMPRESSED_BIN=$(CURDIR)/artifacts/build/$(VER)/opencode-native-revived-upx OPENCODE_CRHANDLER_SO=$(CURDIR)/artifacts/build/$(VER)/libopencode-crhandler.so $(MAKE) --no-print-directory deb-compressed VER=$(VER)
TRANSPLANT_ROOT=$(CURDIR)/artifacts/build OPENCODE_COMPRESSED_BIN=$(CURDIR)/artifacts/build/$(VER)/opencode-native-revived-upx OPENCODE_CRHANDLER_SO=$(CURDIR)/artifacts/build/$(VER)/libopencode-crhandler.so $(MAKE) --no-print-directory pacman-compressed VER=$(VER)
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi
# harden-native: seccomp-harden the B-line product (v1 crhandler zero-displacement
# patch chain): compile tools/shim/sigsys_handler.c into artifacts/build/<ver>/,
# patch opencode-native-revived in place (DT_NEEDED[0]="libopencode-crhandler.so",
# pre-patch copy kept as *.pre-crhandler). MUST run BEFORE build-native-upx.
# Usage: make harden-native VER=2.0.0
.PHONY: harden-native
harden-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make harden-native VER=2.0.0"; \
exit 1; \
fi
@if ! command -v clang >/dev/null 2>&1; then echo "WARN: clang not found; skipping seccomp hardening"; exit 0; fi
src="$(CURDIR)/artifacts/build/$(VER)/opencode-native-revived"; \
if [ ! -f "$$src" ]; then echo "Error: $$src missing; run 'make build-native VER=$(VER)' first"; exit 1; fi; \
echo "==> harden-native VER=$(VER)"; \
clang -shared -fPIC -O2 -o "$(CURDIR)/artifacts/build/$(VER)/libopencode-crhandler.so" tools/shim/sigsys_handler.c || exit 1; \
if grep -aqF libopencode-crhandler.so "$$src"; then \
echo "==> already hardened, skip"; \
exit 0; \
fi; \
cp -p "$$src" "$$src.pre-crhandler" || exit 1; \
python3 tools/transplant/crhandler_patch.py "$$src" || exit 1; \
echo "harden-native: pre-patch copy kept at $$src.pre-crhandler"
# wrapper-native: bun-termux-loader wrap of the PER-VERSION glibc standalone
# Produces artifacts/wrapper/<ver>/opencode-wrapper-<ver> (bionic, TUI-capable)
# Input resolution (root-cause fix for the same-source bug — never share one input):
# V2_WRAPPER_INPUT set -> use it verbatim
# VER=2.0.0 -> $(V2_WRAP_ROOT)/glibc-standalone/opencode
# VER=2.0.x -> extract package/bin/opencode from
# $(V2_GLIBC_TGZ)/opencode-cli-linux-arm64-<ver>.tgz
# Exact version gate: wrapped --version MUST equal "opencode v$(VER)".
.PHONY: wrapper-native
wrapper-native:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make wrapper-native VER=2.0.0"; \
exit 1; \
fi
@if [ ! -d "$(V2_LOADER_ROOT)" ]; then \
echo "Error: bun-termux-loader not found at $(V2_LOADER_ROOT) (clone https://github.com/emberglazee/bun-termux-loader)"; \
exit 1; \
fi
@mkdir -p artifacts/wrapper/$(VER)
@set -e; \
if [ -n "$(V2_WRAPPER_INPUT)" ]; then \
src="$(V2_WRAPPER_INPUT)"; \
elif [ "$(VER)" = "2.0.0" ]; then \
src="$(V2_WRAP_ROOT)/glibc-standalone/opencode"; \
else \
tgz="$(V2_GLIBC_TGZ)/opencode-cli-linux-arm64-$(VER).tgz"; \
if [ ! -f "$$tgz" ]; then echo "Error: glibc tgz not found: $$tgz"; exit 1; fi; \
src="$$(mktemp artifacts/wrapper/$(VER)/.input-XXXXXX)"; \
tar -xzf "$$tgz" -O package/bin/opencode > "$$src"; \
chmod +x "$$src"; \
fi; \
if [ ! -x "$$src" ]; then echo "Error: wrap input not executable: $$src"; exit 1; fi; \
python3 $(V2_LOADER_ROOT)/build.py "$$src" artifacts/wrapper/$(VER)/opencode-wrapper-$(VER) --wrapper $(V2_LOADER_ROOT)/wrapper --shim $(V2_LOADER_ROOT)/bunfs_shim.so; \
rm -f artifacts/wrapper/$(VER)/.input-*
@v="$$(artifacts/wrapper/$(VER)/opencode-wrapper-$(VER) --version 2>&1 | grep -oE 'opencode v[0-9][0-9a-z.]*' | head -1)"; \
if [ "$$v" != "opencode v$(VER)" ]; then \
echo "Error: wrapper version gate failed: want [opencode v$(VER)] got [$$v]"; \
exit 1; \
fi; \
echo "==> wrapper version gate: $$v"
@sha256sum artifacts/wrapper/$(VER)/opencode-wrapper-$(VER) | awk '{print $$1}' | tee artifacts/wrapper/$(VER)/wrapper.sha256
@echo "==> wrapper: artifacts/wrapper/$(VER)/opencode-wrapper-$(VER) ($$(stat -c%s artifacts/wrapper/$(VER)/opencode-wrapper-$(VER)) B)"
# family-v2-wrapper: per-version wrap + exact version gate + deb/pacman packaging
# (scripts/package/package_wrapper_v2.sh: payload $PREFIX/bin/opencode + usr/bin/opencode,
# control/PKGBUILD fields = archived verbatim decision artifacts)
.PHONY: family-v2-wrapper
family-v2-wrapper:
@if [ -z "$(VER_IS_SET)" ]; then \
echo "Error: VER is required. Example: make family-v2-wrapper VER=2.0.0"; \
exit 1; \
fi
$(MAKE) --no-print-directory wrapper-native VER=$(VER)
bash scripts/package/package_wrapper_v2.sh $(VER)
@if [ -n "$(VER_IS_SET)" ] && [ "$(NOT_CLEAN)" != "1" ]; then $(MAKE) --no-print-directory clean-version VER=$(VER) || exit 1; fi