WIP 86 Android: 7-Zip 25.01 을 공식 소스에서 NDK 로 빌드 (lib7zz.so) - Android 에서도 7z · RAR 풀기 (검토 대기)

- 관리자 지시 4: 7z2501-src.7z (SHA-256 2aed39b8…0181 고정, 고치지 않음) 의 CPP/7zip/Bundles/Alone2 (7zz) 를 NDK clang
  (aarch64-linux-android24) 으로 빌드 - tool/build_7zz_android.sh. 어셈블리 끔, Windows 에서 빌드할 때 makefile 이 MinGW 로
  여기지 않게 SystemDrive · SYSTEMDRIVE 를 비움, 새 clang 경고로 실패하지 않게 경고 묶음 끔. libc++ 는 정적 (libc · libm · libdl 만 씀).
  결과 third_party/7zip/android/arm64-v8a/lib7zz.so (2.8MB) + License.txt.
- APK: jniLibs 에 third_party/7zip/android, third_party_libs.txt · check_apk 목록에 lib7zz.so (check_apk 통과 - 실행 파일 5개).
- 앱: bundledSevenZip 이 Android 에서는 네이티브 라이브러리 폴더의 lib7zz.so (rsync 와 같은 방식). 있으면 "다른 앱으로 열기" 안내
  대신 실제 풀기 - 같은 검사 (zip slip · 링크 · 이름 · 공간 · 비밀번호 표준 입력) 그대로. 없으면 예전처럼 안내.
- 고지: THIRD_PARTY_NOTICES_ANDROID 에 7-Zip 25.01 (LGPL 2.1+ · BSD · unRAR 제한, 소스 주소 · 빌드 방법 · 대응 소스 = 태그).
- 시험: licenses_test (Android 고지 · 빌드 스크립트 해시 · APK 목록). Android 기기에서 실제로 푸는 것은 15 의 기기 확인 필요.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음6
RyuJongJin 2026-10-17 15:17:01 +09:00
parent 47144981d0
commit e0466d6168
8 changed files with 70 additions and 6 deletions

View File

@ -65,9 +65,11 @@ android {
sourceSets["main"].jniLibs.srcDir("../../third_party/sdcpp/android")
// Tor (libtor.so 라는 이름의 실행 파일): 공식 Tor Expert Bundle (Android aarch64) 의 libTor.so, tool/fetch_tor_android.ps1 (판 · SHA-256 고정)
sourceSets["main"].jniLibs.srcDir("../../third_party/tor/android")
// 86: 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일 - 7z · RAR 풀기): tool/build_7zz_android.sh (공식 소스, SHA-256 고정)
sourceSets["main"].jniLibs.srcDir("../../third_party/7zip/android")
// 195: third_party 의 실행 파일은 third_party_libs.txt 에 적은 것만 (공유 폴더에 다른 판이 있어도 섞이지 않게)
val thirdPartyLibs = file("third_party_libs.txt").readLines().map { it.trim() }.filter { it.isNotEmpty() && !it.startsWith("#") }.toSet()
listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android").forEach { dir ->
listOf("../../third_party/rsync/android", "../../third_party/sdcpp/android", "../../third_party/tor/android", "../../third_party/7zip/android").forEach { dir ->
fileTree(dir).matching { include("**/*.so") }.forEach { so ->
if (so.name !in thirdPartyLibs) packaging.jniLibs.excludes += "**/${so.name}"
}

View File

@ -6,3 +6,4 @@ librsync.so
libsdcli.so
libsdcli_v80.so
libtor.so
lib7zz.so

View File

@ -43,6 +43,12 @@ JJ_MKVMaker (Android) - 포함된 오픈 소스 구성 요소 및 라이선스
- rsync 3.4.1 (librsync.so 라는 이름의 실행 파일) GPL-3.0-or-later https://rsync.samba.org
공식 소스 (https://download.samba.org/pub/rsync/src/rsync-3.4.1.tar.gz) 를 고치지 않고 Android NDK 로 빌드
(빌드 방법: 소스의 tool/build_rsync_android.sh)
- 7-Zip 25.01 (lib7zz.so 라는 이름의 실행 파일) LGPL-2.1-or-later · BSD-3-Clause · BSD-2-Clause · unRAR 라이선스 제한
Copyright (C) 1999-2025 Igor Pavlov. https://www.7-zip.org
7z · RAR 압축을 푸는 데만 씁니다. RAR 을 푸는 코드는 unRAR 라이선스 제한을 따르며 RAR 을 만드는 데 쓰지 않습니다
(이 앱은 RAR 을 만들지 않습니다). 공식 소스 (https://www.7-zip.org/a/7z2501-src.7z) 를 고치지 않고 Android NDK 로 빌드
(빌드 방법: 소스의 tool/build_7zz_android.sh). 따로 실행하는 프로그램이며, 대응 소스는 위 주소와 이 앱 판의 태그에 있습니다.
원문: 앱 안 "오픈 소스 라이선스" 의 7-Zip (7ZIP_LICENSE.txt) · GNU LGPL 2.1
- stable-diffusion.cpp · ggml (libsdcli.so 라는 이름의 실행 파일) MIT https://github.com/leejet/stable-diffusion.cpp
커밋 228c707 을 고치지 않고 Android NDK 로 빌드 (AI 그림 · 해상도 올리기, 빌드 방법: 소스의 tool/build_sdcpp_android.sh)
함께 묶인 구성 요소: ggml (MIT) · libwebp · libwebm (BSD-3) · Oniguruma · Darts-clone (BSD-2) · utf8proc · nlohmann/json ·

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show MethodChannel;
import 'package:path/path.dart' as p;
import '../app/app_controller.dart';
@ -12,8 +13,17 @@ import '../core/vfs.dart';
import '../l10n/tr.dart';
import 'theme.dart';
/// 86: 동봉한 7-Zip (Windows: 실행 파일 옆 7zip\7z.exe). 개발 중에는 third_party 의 것. 없으면 null (Android - 아직 없음)
String? bundledSevenZip() {
/// 86: 동봉한 7-Zip. 없으면 null
/// - Windows: 실행 파일 옆 7zip\7z.exe (개발 중에는 third_party 의 것)
/// - Android: 네이티브 라이브러리 폴더의 lib7zz.so (tool/build_7zz_android.sh, APK 의 jniLibs - rsync 와 같은 방식)
Future<String?> bundledSevenZip() async {
if (Platform.isAndroid) {
try {
final dir = await const MethodChannel('jj_mkvmaker/android').invokeMethod<String>('nativeLibDir');
if (dir != null && File(p.join(dir, 'lib7zz.so')).existsSync()) return p.join(dir, 'lib7zz.so');
} catch (_) {}
return null;
}
if (!Platform.isWindows) return null;
for (final f in [
p.join(p.dirname(Platform.resolvedExecutable), '7zip', '7z.exe'),
@ -98,7 +108,8 @@ Future<String?> extractArchiveFlow(BuildContext context, AppController c, String
final local = isDav(path) ? await vLocalCopy(path, await c.services.storage.tempDirectory()) : path;
if (!context.mounted) return null;
// 7z · RAR: 동봉한 7-Zip 이 있어야 (Android 는 아직 - 막지 않고 다른 앱으로 안내)
final exe = kind == ArchiveKind.zip ? null : bundledSevenZip();
final exe = kind == ArchiveKind.zip ? null : await bundledSevenZip();
if (!context.mounted) return null;
if (kind != ArchiveKind.zip && exe == null) {
final open = await showDialog<bool>(
context: context,

View File

@ -70,7 +70,7 @@ void main() {
await tester.tap(find.text('취소'));
await wait(tester, () => finished);
expect(Directory(p.join(tmp.path, 'a')).existsSync(), isFalse);
if (bundledSevenZip() == null) {
if (await tester.runAsync(bundledSevenZip) == null) {
final sz = File(p.join(tmp.path, 'x.7z'))..writeAsStringSync('7z');
finished = false;
run((ctx) => extractArchiveFlow(ctx, c, sz.path)).then((_) => finished = true);

View File

@ -96,6 +96,13 @@ void main() {
expect(cmake, contains('third_party/7zip/windows'));
expect(cmake, contains('License.txt'));
expect(File('tool/fetch_7zip.ps1').readAsStringSync(), contains('e7eb0b7ed5efa4e087b7b17f191797f7af5b7f442d1290c66f3a21777005ef57'));
// Android 판 (lib7zz.so - 공식 소스를 NDK 로 빌드): 고지 · 빌드 스크립트의 소스 해시 · APK 목록
final android = File('assets/licenses/THIRD_PARTY_NOTICES_ANDROID.txt').readAsStringSync();
for (final s in ['7-Zip 25.01 (lib7zz.so', '7z2501-src.7z', 'build_7zz_android.sh', 'RAR 을 만들지 않습니다']) {
expect(android, contains(s), reason: s);
}
expect(File('tool/build_7zz_android.sh').readAsStringSync(), contains('2aed39b8f1238464475e9de7dda169a5e873a1dc8bbf4f664b943eaba5620181'));
expect(File('android/app/third_party_libs.txt').readAsLinesSync(), contains('lib7zz.so'));
});
test('Windows 설치: 실행 파일 옆 · tools · rsync 에 원문을 둔다 (CMake)', () {

37
tool/build_7zz_android.sh Normal file
View File

@ -0,0 +1,37 @@
#!/bin/sh
# 86 · 관리자 지시 4: Android (arm64) 용 7-Zip (7zz - 7z · RAR 풀기) 을 공식 소스에서 빌드해
# third_party/7zip/android/arm64-v8a/lib7zz.so 로 둔다 (rsync 와 같은 방식 - APK 의 jniLibs 로 들어가 앱의 네이티브 라이브러리
# 폴더에서 실행된다. Android 의 libc · libm · libdl 만 쓰고 libc++ 는 정적으로).
# 소스: https://www.7-zip.org/a/7z2501-src.7z (7-Zip 25.01, SHA-256 고정, 고치지 않음)
# 라이선스: GNU LGPL 2.1+ · BSD 3/2-clause · unRAR 라이선스 제한 (RAR 은 풀기만 - 앱은 RAR 을 만들지 않는다)
# 빌드: CPP/7zip/Bundles/Alone2 (7zz) 를 NDK clang 으로 (cmpl_clang_arm64.mak). 어셈블리는 끄고 (USE_ASM=),
# Windows 에서 빌드할 때 makefile 이 SystemDrive 로 MinGW 빌드라고 여기지 않게 SystemDrive · SYSTEMDRIVE 를 비운다.
# 새 NDK clang 의 새 경고로 실패하지 않게 경고 묶음 (-Weverything -Werror) 은 끈다 (코드는 그대로).
# 사용법 (Git Bash): sh tool/build_7zz_android.sh - Android NDK 가 필요 (ANDROID_NDK 또는 tools/android-sdk/ndk/*)
# .7z 소스를 풀 7-Zip 이 필요 (Windows: third_party/7zip/windows/7z.exe - tool/fetch_7zip.ps1, 아니면 PATH 의 7z · 7zz)
set -e
APP=$(cd "$(dirname "$0")/.." && pwd)
VER=2501
SHA=2aed39b8f1238464475e9de7dda169a5e873a1dc8bbf4f664b943eaba5620181
NDK_ROOT=${ANDROID_NDK:-$(ls -d "$APP"/../tools/android-sdk/ndk/* | sort | tail -1)}
case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) HOST=windows-x86_64; EXE=.exe ;; Darwin) HOST=darwin-x86_64; EXE= ;; *) HOST=linux-x86_64; EXE= ;; esac
TC=$NDK_ROOT/toolchains/llvm/prebuilt/$HOST/bin
MAKE=$NDK_ROOT/prebuilt/$HOST/bin/make$EXE
[ -x "$MAKE" ] || MAKE=make
SZ="$APP/third_party/7zip/windows/7z.exe"
[ -x "$SZ" ] || SZ=$(command -v 7zz || command -v 7z)
WORK=${TMPDIR:-/tmp}/jj_7zz_android
rm -rf "$WORK" && mkdir -p "$WORK" && cd "$WORK"
curl -sSLf -o src.7z "https://www.7-zip.org/a/7z$VER-src.7z"
echo "$SHA src.7z" | sha256sum -c -
"$SZ" x -y -osrc src.7z > /dev/null
cd src/CPP/7zip/Bundles/Alone2
mkdir -p b/c_arm64
"$MAKE" -j8 -f ../../cmpl_clang_arm64.mak SHELL=/bin/sh SystemDrive= SYSTEMDRIVE= MSYSTEM= \
CC="$TC/clang$EXE --target=aarch64-linux-android24" CXX="$TC/clang++$EXE --target=aarch64-linux-android24" \
USE_ASM= LIB2=-ldl LDFLAGS_STATIC_3=-static-libstdc++ CFLAGS_WARN= CFLAGS_WARN_WALL= > make.log 2>&1 || { tail -30 make.log; exit 1; }
OUT="$APP/third_party/7zip/android/arm64-v8a"
mkdir -p "$OUT"
"$TC/llvm-strip$EXE" -o "$OUT/lib7zz.so" b/c_arm64/7zz
cp ../../../../DOC/License.txt "$OUT/../License.txt"
ls -l "$OUT/lib7zz.so"

View File

@ -39,7 +39,7 @@ if (-not $Libs) { $Libs = Join-Path $app 'android\app\third_party_libs.txt' }
$listed = @(Get-Content $Libs | ForEach-Object { $_.Trim() } | Where-Object { $_ -and -not $_.StartsWith('#') })
# third_party 에 있는 파일 (목록 밖이면 APK 에 없어야 함)
$tp = @()
foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android') {
foreach ($d in 'third_party\rsync\android', 'third_party\sdcpp\android', 'third_party\tor\android', 'third_party\7zip\android') {
$dir = Join-Path $app $d
if (Test-Path $dir) { $tp += @(Get-ChildItem $dir -Recurse -Filter *.so | ForEach-Object { $_.Name }) }
}