@@ -5,6 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
55REPO_ROOT=" $( cd " $SCRIPT_DIR /../.." && pwd) "
66APK_DIR=" ${APK_DIR:- $REPO_ROOT / build/ app/ outputs/ flutter-apk} "
77APP_NAME=" ${APP_NAME:- ServerBox} "
8+ KEY_PROPERTIES=" ${KEY_PROPERTIES:- $REPO_ROOT / android/ key.properties} "
89
910require_cmd () {
1011 local name=" $1 "
@@ -14,16 +15,240 @@ require_cmd() {
1415 fi
1516}
1617
18+ android_sdk_roots () {
19+ local candidates=(
20+ " ${ANDROID_HOME:- } "
21+ " ${ANDROID_SDK_ROOT:- } "
22+ " $HOME /Library/Android/sdk"
23+ " $HOME /Android/Sdk"
24+ " /opt/android-sdk"
25+ " /usr/local/lib/android/sdk"
26+ )
27+ local roots=' '
28+ local candidate
29+
30+ for candidate in " ${candidates[@]} " ; do
31+ if [[ -z " $candidate " || ! -d " $candidate " ]]; then
32+ continue
33+ fi
34+
35+ if printf ' %s' " $roots " | grep -Fxq " $candidate " ; then
36+ continue
37+ fi
38+ roots+=" $candidate " $' \n '
39+ done
40+
41+ printf ' %s' " $roots "
42+ }
43+
44+ android_tool_version () {
45+ local path=" $1 "
46+ local version=' 0'
47+ case " $path " in
48+ * /build-tools/* )
49+ version=" ${path#*/ build-tools/ } "
50+ version=" ${version%%/* } "
51+ ;;
52+ * /ndk/* )
53+ version=" ${path#*/ ndk/ } "
54+ version=" ${version%%/* } "
55+ ;;
56+ esac
57+
58+ printf ' %s\n' " $version "
59+ }
60+
61+ version_sort_key () {
62+ local version=" $1 "
63+ local part
64+ local padded
65+ local key=' '
66+
67+ IFS=' .' read -ra parts <<< " $version"
68+ for part in " ${parts[@]} " ; do
69+ if [[ " $part " =~ ^[0-9]+$ ]]; then
70+ printf -v padded ' %010d' " $part "
71+ else
72+ padded=" $part "
73+ fi
74+ key+=" $padded ."
75+ done
76+
77+ printf ' %s\n' " $key "
78+ }
79+
80+ select_latest_android_path () {
81+ local path
82+ local version
83+ local key
84+
85+ while IFS= read -r path; do
86+ if [[ -z " $path " ]]; then
87+ continue
88+ fi
89+ version=" $( android_tool_version " $path " ) "
90+ key=" $( version_sort_key " $version " ) "
91+ printf ' %s\t%s\n' " $key " " $path "
92+ done |
93+ sort |
94+ tail -n 1 |
95+ while IFS=$' \t ' read -r _ path; do
96+ printf ' %s\n' " $path "
97+ done
98+ }
99+
100+ find_latest_android_tool () {
101+ local name=" $1 "
102+ local sdk_root
103+ local matches=()
104+ local path
105+
106+ while IFS= read -r sdk_root; do
107+ if [[ -z " $sdk_root " || ! -d " $sdk_root /build-tools" ]]; then
108+ continue
109+ fi
110+ while IFS= read -r path; do
111+ matches+=(" $path " )
112+ done < <( find " $sdk_root /build-tools" -type f -name " $name " )
113+ done < <( android_sdk_roots)
114+
115+ if [[ ${# matches[@]} -eq 0 ]]; then
116+ return 1
117+ fi
118+
119+ printf ' %s\n' " ${matches[@]} " | select_latest_android_path
120+ }
121+
122+ find_llvm_objcopy () {
123+ local sdk_root
124+ local matches=()
125+ local path
126+
127+ while IFS= read -r sdk_root; do
128+ if [[ -z " $sdk_root " || ! -d " $sdk_root /ndk" ]]; then
129+ continue
130+ fi
131+ while IFS= read -r path; do
132+ matches+=(" $path " )
133+ done < <( find " $sdk_root /ndk" -type f -path ' */toolchains/llvm/prebuilt/*/bin/llvm-objcopy' )
134+ done < <( android_sdk_roots)
135+
136+ if [[ ${# matches[@]} -ne 0 ]]; then
137+ printf ' %s\n' " ${matches[@]} " | select_latest_android_path
138+ return
139+ fi
140+
141+ command -v llvm-objcopy || command -v objcopy
142+ }
143+
144+ find_readelf () {
145+ local sdk_root
146+ local matches=()
147+ local path
148+
149+ while IFS= read -r sdk_root; do
150+ if [[ -z " $sdk_root " || ! -d " $sdk_root /ndk" ]]; then
151+ continue
152+ fi
153+ while IFS= read -r path; do
154+ matches+=(" $path " )
155+ done < <( find " $sdk_root /ndk" -type f -path ' */toolchains/llvm/prebuilt/*/bin/llvm-readelf' )
156+ done < <( android_sdk_roots)
157+
158+ if [[ ${# matches[@]} -ne 0 ]]; then
159+ printf ' %s\n' " ${matches[@]} " | select_latest_android_path
160+ return
161+ fi
162+
163+ command -v readelf || command -v llvm-readelf
164+ }
165+
166+ property_value () {
167+ local name=" $1 "
168+ grep -E " ^[[:space:]]*${name} [[:space:]]*=" " $KEY_PROPERTIES " |
169+ tail -n 1 |
170+ sed -E ' s/^[^=]*=[[:space:]]*//; s/[[:space:]]*$//' || true
171+ }
172+
173+ has_build_id () {
174+ local so_file=" $1 "
175+ " $READELF " -n " $so_file " 2> /dev/null | grep -q ' Build ID'
176+ }
177+
17178require_cmd find
18- require_cmd readelf
179+ require_cmd grep
180+ require_cmd sed
181+ require_cmd sort
19182require_cmd unzip
183+ require_cmd zip
184+
185+ OBJCOPY=" ${OBJCOPY:- } "
186+ READELF=" ${READELF:- } "
187+ APKSIGNER=" ${APKSIGNER:- } "
188+ ZIPALIGN=" ${ZIPALIGN:- } "
189+
190+ if [[ -z " $OBJCOPY " ]]; then
191+ OBJCOPY=" $( find_llvm_objcopy || true) "
192+ fi
193+ if [[ -z " $READELF " ]]; then
194+ READELF=" $( find_readelf || true) "
195+ fi
196+ if [[ -z " $APKSIGNER " ]]; then
197+ APKSIGNER=" $( find_latest_android_tool apksigner || true) "
198+ fi
199+ if [[ -z " $ZIPALIGN " ]]; then
200+ ZIPALIGN=" $( find_latest_android_tool zipalign || true) "
201+ fi
202+
203+ if [[ -z " $OBJCOPY " || ! -x " $OBJCOPY " ]]; then
204+ echo ' llvm-objcopy or objcopy is required to remove ELF build-id notes' >&2
205+ exit 1
206+ fi
207+
208+ if [[ -z " $READELF " || ! -x " $READELF " ]]; then
209+ echo ' readelf or llvm-readelf is required to inspect ELF build-id notes' >&2
210+ exit 1
211+ fi
212+
213+ if [[ -z " $APKSIGNER " || ! -x " $APKSIGNER " ]]; then
214+ echo ' Android build-tools apksigner is required to re-sign patched APKs' >&2
215+ exit 1
216+ fi
217+
218+ if [[ -z " $ZIPALIGN " || ! -x " $ZIPALIGN " ]]; then
219+ echo ' Android build-tools zipalign is required before re-signing patched APKs' >&2
220+ exit 1
221+ fi
222+
223+ if [[ ! -f " $KEY_PROPERTIES " ]]; then
224+ echo " key.properties not found: $KEY_PROPERTIES " >&2
225+ exit 1
226+ fi
227+
228+ store_file=" $( property_value storeFile) "
229+ store_password=" $( property_value storePassword) "
230+ key_alias=" $( property_value keyAlias) "
231+ key_password=" $( property_value keyPassword) "
232+
233+ if [[ -z " $store_file " || -z " $store_password " || -z " $key_alias " || -z " $key_password " ]]; then
234+ echo " storeFile, storePassword, keyAlias, and keyPassword are required in $KEY_PROPERTIES " >&2
235+ exit 1
236+ fi
237+
238+ if [[ " $store_file " != /* ]]; then
239+ store_file=" $REPO_ROOT /android/app/$store_file "
240+ fi
241+
242+ if [[ ! -f " $store_file " ]]; then
243+ echo " signing store file not found: $store_file " >&2
244+ exit 1
245+ fi
20246
21247shopt -s nullglob
22248
23249tmp_dir=" $( mktemp -d) "
24250trap ' rm -rf "$tmp_dir"' EXIT
25251
26- failures=()
27252apks=()
28253patterns=(
29254 " ${APP_NAME} _*_arm64.apk"
@@ -52,18 +277,59 @@ for apk in "${apks[@]}"; do
52277 extract_dir=" $tmp_dir /$apk_name "
53278 mkdir -p " $extract_dir "
54279 unzip -qq " $apk " " lib/*/*.so" -d " $extract_dir "
280+ patched_files=()
55281
56282 while IFS= read -r so_file; do
57- if readelf -n " $so_file " | grep -q ' Build ID' ; then
58- failures+=(" $apk_name :${so_file# $extract_dir / } " )
283+ if has_build_id " $so_file " ; then
284+ rel_path=" ${so_file# " $extract_dir " / } "
285+ tmp_so=" $so_file .tmp"
286+ " $OBJCOPY " --remove-section=.note.gnu.build-id " $so_file " " $tmp_so "
287+ mv " $tmp_so " " $so_file "
288+ if has_build_id " $so_file " ; then
289+ echo " failed to remove ELF build-id note: $apk_name :$rel_path " >&2
290+ exit 1
291+ fi
292+ patched_files+=(" $rel_path " )
59293 fi
60294 done < <( find " $extract_dir " -type f -name ' *.so' | sort)
295+
296+ if [[ ${# patched_files[@]} -eq 0 ]]; then
297+ continue
298+ fi
299+
300+ echo " Removing ELF build-id notes from $apk_name "
301+ (
302+ cd " $extract_dir "
303+ zip -q -0 " $apk " " ${patched_files[@]} "
304+ )
305+
306+ aligned_apk=" $tmp_dir /$apk_name .aligned.apk"
307+ " $ZIPALIGN " -f -p 4 " $apk " " $aligned_apk "
308+ mv " $aligned_apk " " $apk "
309+
310+ " $APKSIGNER " sign \
311+ --ks " $store_file " \
312+ --ks-key-alias " $key_alias " \
313+ --ks-pass " pass:$store_password " \
314+ --key-pass " pass:$key_password " \
315+ " $apk "
61316done
62317
63- if [[ ${# failures[@]} -ne 0 ]]; then
64- echo ' native libraries still contain ELF build-id notes:' >&2
65- printf ' %s\n' " ${failures[@]} " >&2
66- exit 1
67- fi
318+ for apk in " ${apks[@]} " ; do
319+ apk_name=" $( basename " $apk " ) "
320+ verify_dir=" $tmp_dir /verify-$apk_name "
321+ mkdir -p " $verify_dir "
322+ unzip -qq " $apk " " lib/*/*.so" -d " $verify_dir "
323+
324+ while IFS= read -r so_file; do
325+ if has_build_id " $so_file " ; then
326+ rel_path=" ${so_file# " $verify_dir " / } "
327+ echo " native library still contains ELF build-id note: $apk_name :$rel_path " >&2
328+ exit 1
329+ fi
330+ done < <( find " $verify_dir " -type f -name ' *.so' | sort)
331+
332+ " $APKSIGNER " verify " $apk "
333+ done
68334
69335echo ' F-Droid native library verification passed.'
0 commit comments