android-ndk开发(2): macOS 安装 ndk
2025/05/05
1. 概要
对于 android-ndk 在 r23 之前的版本,官方提供了 .zip 文件, 解压即安装。
对于 android-ndk 在 r23 以及之后的版本, 官方只提供了 .dmg 文件, 不能简单的解压完成安装。
2. ndk >= r23, macOS 怎么安装 ?
android ndk 从 r23 开始, 不再提供 macOS 平台的 .zip 安装包(如android-ndk-r22b-darwin-x86_64.zip), 只提供 .dmg 格式的安装包。链接 [1] 可以确认。这导致无法像 Windows 和 Linux 一样, 解压 .zip 就完成 ndk 的安装。
以 android-ndk-r27c-darwin.dmg 为例, 双击后显示两个文件: AndroidNDK12479018.app 和 source.properties。 双击 AndroidNDK12479018.app 后感觉完成了安装, 但在 /Application
, /Library
, ~/Application
, ~/Library
4个目录找不到安装的内容。
手动解压 AndroidNDK12479018.app,里面的 NDK 目录是工具链, CodeSign 这玩意儿暂时不知道用处,感觉就是故意使绊子。
为了一劳永逸解决 macOS 上 ndk >= r23 版本的安装, 上述过程写为 Python 脚本。基本思路是:挂载 .dmg -> 找到 .app 文件 -> 找 NDK 目录 -> 拷贝到目标目录。
import subprocess
import shutil
import os
import time
def mount_and_copy(dmg_path, target_dir):
target_dir = os.path.expanduser(target_dir)
try:
print(f"⏳ 正在挂载 {dmg_path}...")
mount_result = subprocess.run(
["hdiutil", "attach", "-nobrowse", dmg_path],
capture_output=True,
text=True,
check=True
)
# 解析挂载路径
mount_path = None
for line in mount_result.stdout.splitlines():
if "/Volumes" in line:
mount_path = line.split("\t")[-1].strip()
break
if not mount_path:
raise FileNotFoundError("无法定位挂载点")
time.sleep(2) # 等待文件系统稳定
# 动态查找AndroidNDK*.app
app_files = [f for f in os.listdir(mount_path)
if f.startswith("AndroidNDK") and f.endswith(".app")]
# e.g. AndroidNDK12479018.app for android-ndk-r27c
if not app_files:
available = "\n".join(os.listdir(mount_path))
raise FileNotFoundError(
f"未找到AndroidNDK*.app文件,当前卷内容:\n{available}"
)
app_path = os.path.join(mount_path, app_files[0])
print(f" 定位到应用包:{app_path}")
# 验证NDK路径
ndk_source = os.path.join(app_path, "Contents", "NDK")
if not os.path.exists(ndk_source):
raise FileNotFoundError(f"NDK目录不存在:{ndk_source}")
# 准备目标目录
os.makedirs(target_dir, exist_ok=True)
print(f" 目标目录:{target_dir}")
# 执行复制(保留元数据)
print("⚙️ 开始复制NDK文件...")
shutil.copytree(
ndk_source,
target_dir,
dirs_exist_ok=True,
copy_function=shutil.copy2
)
# 卸载镜像
print("⏳ 正在卸载...")
subprocess.run(["hdiutil", "detach", mount_path], check=True)
print(f" 安装完成!路径:{target_dir}")
except subprocess.CalledProcessError as e:
print(f"❌ 命令执行失败:{e.stderr}")
if 'mount_path' in locals() and os.path.exists(mount_path):
print(f"⚠️ 尝试手动卸载:hdiutil detach {mount_path}")
except Exception as e:
print(f"❌ 错误:{str(e)}")
if __name__ == "__main__":
dmg_path = os.path.join(os.getcwd(), "android-ndk-r27c-darwin.dmg")
mount_and_copy(dmg_path, "/Users/zz/soft/toolchains/android-ndk-r27c")