diff --git a/backend/services/build_service.py b/backend/services/build_service.py index 2810cf8..6406d23 100644 --- a/backend/services/build_service.py +++ b/backend/services/build_service.py @@ -41,6 +41,37 @@ class BuildError(Exception): source_prepare_lock = asyncio.Lock() +def _patch_afnetworking_private_headers(build_dir: Path) -> int: + """移除旧版 AFNetworking 对 Xcode 新 SDK 私有头的直接引用。""" + afnetworking_dir = build_dir / "Pods" / "AFNetworking" + if not afnetworking_dir.is_dir(): + return 0 + + private_import = "#import " + patched_files = 0 + for source_path in afnetworking_dir.rglob("*"): + if source_path.suffix not in {".h", ".m", ".mm"} or not source_path.is_file(): + continue + + content = source_path.read_text(encoding="utf-8") + if private_import not in content: + continue + + updated_lines = [ + line for line in content.splitlines(keepends=True) + if line.strip() != private_import + ] + original_mode = source_path.stat().st_mode + try: + source_path.chmod(original_mode | 0o200) + source_path.write_text("".join(updated_lines), encoding="utf-8") + finally: + source_path.chmod(original_mode) + patched_files += 1 + + return patched_files + + def _decode_provisioning_profile(profile_path: Path) -> dict: """沿用 AutoPacking 脚本的 security cms 方式读取描述文件。""" try: @@ -654,6 +685,13 @@ async def run_pod_install(task_id: str, build_dir: Path): if process.returncode != 0: raise BuildError("pod install 失败", category="config") + patched_files = await asyncio.to_thread(_patch_afnetworking_private_headers, build_dir) + if patched_files: + await log_streamer.emit( + task_id, + f"已修复 AFNetworking 私有头引用: {patched_files} 个文件", + ) + await log_streamer.emit(task_id, "依赖安装完成") diff --git a/tests/test_build_service.py b/tests/test_build_service.py index 67212d1..6857a25 100644 --- a/tests/test_build_service.py +++ b/tests/test_build_service.py @@ -17,6 +17,8 @@ from backend.services.build_service import ( prepare_source_snapshot, build_project, generate_config, + run_pod_install, + _patch_afnetworking_private_headers, _cleanup_old_builds, _resolve_provisioning_profile, ) @@ -206,6 +208,34 @@ async def test_copy_source_code_overwrites_existing(tmp_dirs, log_streamer): assert (result / "readoor").exists() +def test_patch_afnetworking_private_headers(tmp_path): + """pod install 后移除新版 Xcode 禁止直接引用的 netinet6 私有头。""" + source_dir = tmp_path / "Pods" / "AFNetworking" / "AFNetworking" + source_dir.mkdir(parents=True) + source_file = source_dir / "AFNetworkReachabilityManager.m" + source_file.write_text( + "#import \n#import \n#import \n" + ) + source_file.chmod(0o444) + + assert _patch_afnetworking_private_headers(tmp_path) == 1 + assert "#import " not in source_file.read_text() + assert "#import " in source_file.read_text() + assert source_file.stat().st_mode & 0o777 == 0o444 + + +async def test_run_pod_install_patches_afnetworking(tmp_path, log_streamer): + source_dir = tmp_path / "Pods" / "AFNetworking" + source_dir.mkdir(parents=True) + source_file = source_dir / "AFHTTPSessionManager.m" + source_file.write_text("#import \n") + + with patch("asyncio.create_subprocess_exec", return_value=_make_mock_process()): + await run_pod_install("t1", tmp_path) + + assert "netinet6/in6.h" not in source_file.read_text() + + async def test_build_project_passes_scheme_as_exec_argument(tmp_path, log_streamer): """Scheme 中的 shell 特殊字符只能作为 xcodebuild 参数,不能被执行。""" build_dir = tmp_path / "build"