#!/usr/bin/env python3
"""
ESP-IDF自动安装脚本 (Python版本)
"""
import argparse
import subprocess
import os
import sys
import platform
import shutil
import re
# 默认设置
DEFAULT_VERSION = "latest"
ESP_IDF_REPO = "https://github.com/espressif/esp-idf.git"
DEFAULT_VERSION_COUNT = 10
def detect_os():
"""检测操作系统类型"""
system = platform.system().lower()
if system == "linux":
# 检测具体的Linux发行版
if os.path.exists("/etc/debian_version"):
return "debian"
elif os.path.exists("/etc/centos-release"):
return "centos"
elif os.path.exists("/etc/arch-release"):
return "arch"
else:
return "unknown"
elif system == "darwin":
return "macos"
else:
return "unknown"
def install_dependencies(os_type):
"""安装依赖包"""
try:
if os_type == "debian":
print("正在安装Debian/Ubuntu依赖包...")
subprocess.run(["sudo", "apt", "update"], check=True)
subprocess.run([
"sudo", "apt", "install", "-y",
"git", "wget", "flex", "bison", "gperf",
"python3", "python3-pip", "python3-venv",
"cmake", "ninja-build", "ccache",
"libffi-dev", "libssl-dev", "dfu-util",
"libusb-1.0-0"
], check=True)
elif os_type == "centos":
print("正在安装CentOS/RHEL依赖包...")
subprocess.run([
"sudo", "yum", "install", "-y",
"git", "wget", "flex", "bison", "gperf",
"python3", "python3-setuptools",
"cmake", "ninja-build", "ccache",
"dfu-util", "libusbx"
], check=True)
elif os_type == "arch":
print("正在安装Arch Linux依赖包...")
subprocess.run([
"sudo", "pacman", "-Syu", "--noconfirm",
"gcc", "git", "make", "flex", "bison",
"gperf", "python", "cmake", "ninja",
"ccache", "dfu-util", "libusb", "python-pip"
], check=True)
elif os_type == "macos":
print("正在安装macOS依赖包...")
# 检查是否安装了Homebrew
if not shutil.which("brew"):
print("请先安装Homebrew: https://brew.sh/")
sys.exit(1)
subprocess.run([
"brew", "install",
"cmake", "ninja", "dfu-util", "ccache"
], check=True)
else:
print(f"不支持的操作系统类型: {os_type}")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"安装依赖包时出错: {e}")
sys.exit(1)
def install_qemu_dependencies(os_type):
"""安装QEMU的依赖包"""
try:
if os_type == "debian":
print("正在安装QEMU的Debian/Ubuntu依赖包...")
subprocess.run([
"sudo", "apt", "install", "-y",
"libgcrypt20", "libglib2.0-0", "libpixman-1-0",
"libsdl2-2.0-0", "libslirp0"
], check=True)
elif os_type == "centos":
print("正在安装QEMU的CentOS/RHEL依赖包...")
subprocess.run([
"sudo", "yum", "install", "-y", "--enablerepo=powertools",
"libgcrypt", "glib2", "pixman", "SDL2", "libslirp"
], check=True)
elif os_type == "arch":
print("正在安装QEMU的Arch Linux依赖包...")
subprocess.run([
"sudo", "pacman", "-S", "--needed", "--noconfirm",
"libgcrypt", "glib2", "pixman", "sdl2", "libslirp"
], check=True)
elif os_type == "macos":
print("正在安装QEMU的macOS依赖包...")
# 检查是否安装了Homebrew
if not shutil.which("brew"):
print("请先安装Homebrew: https://brew.sh/")
sys.exit(1)
subprocess.run([
"brew", "install",
"libgcrypt", "glib", "pixman", "sdl2", "libslirp"
], check=True)
else:
print(f"不支持的操作系统类型: {os_type}")
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"安装QEMU依赖包时出错: {e}")
sys.exit(1)
def get_remote_versions(count=DEFAULT_VERSION_COUNT):
"""获取远端版本列表"""
print("正在获取ESP-IDF版本列表...")
try:
# 使用git ls-remote获取tags,并提取版本号
result = subprocess.run(
["git", "ls-remote", "--tags", ESP_IDF_REPO],
capture_output=True,
text=True,
check=True
)
# 过滤版本号
versions = []
for line in result.stdout.split('\n'):
if line:
tag = line.split('/')[-1]
# 过滤掉带有^{}的行,这些是轻量级标签的解引用
if re.match(r'^v[0-9]+(\.[0-9]+)*$', tag) and '{}' not in tag:
versions.append(tag)
# 按版本号排序(降序)
versions.sort(key=lambda x: [int(num) for num in x[1:].split('.')], reverse=True)
# 返回指定数量的版本
return versions[:count]
except subprocess.CalledProcessError as e:
print(f"获取版本列表时出错: {e}")
return []
def install_esp_idf(version, install_qemu=False):
"""安装ESP-IDF"""
install_path = os.path.join(os.path.expanduser("~"), "esp", version)
# 创建安装目录
os.makedirs(install_path, exist_ok=True)
# 克隆指定版本的ESP-IDF
print(f"正在克隆ESP-IDF {version}...")
try:
subprocess.run([
"git", "clone", "-b", version, "--recursive",
ESP_IDF_REPO, os.path.join(install_path, "esp-idf")
], check=True)
# 设置环境变量
os.environ["IDF_GITHUB_ASSETS"] = "dl.espressif.com/github_assets"
os.environ["IDF_TOOLS_PATH"] = os.path.join(install_path, "espressif")
# 进入ESP-IDF目录并执行安装脚本
esp_idf_path = os.path.join(install_path, "esp-idf")
os.chdir(esp_idf_path)
subprocess.run(["./install.sh"], check=True)
print(f"ESP-IDF {version} 已成功安装到 {esp_idf_path}")
# 如果需要安装QEMU
if install_qemu:
print("正在安装QEMU...")
# 安装QEMU依赖
os_type = detect_os()
install_qemu_dependencies(os_type)
# 使用idf_tools.py安装QEMU
subprocess.run([
"python", os.path.join(esp_idf_path, "tools", "idf_tools.py"),
"install", "qemu-xtensa", "qemu-riscv32"
], check=True)
print("QEMU已成功安装")
except subprocess.CalledProcessError as e:
print(f"安装ESP-IDF或QEMU时出错: {e}")
sys.exit(1)
def interactive_mode():
"""交互式界面"""
print("欢迎使用ESP-IDF自动安装脚本!")
# 检测操作系统
os_type = detect_os()
print(f"检测到的操作系统: {os_type}")
# 询问是否安装依赖
print()
reply = input("是否需要安装系统依赖包?[y/N]: ").strip().lower()
if reply in ['y', 'yes']:
print("开始安装系统依赖包...")
install_dependencies(os_type)
print("系统依赖包安装完成。")
else:
print("跳过系统依赖包安装。")
# 询问是否安装QEMU
print()
install_qemu = False
reply = input("是否需要安装QEMU模拟器?[y/N]: ").strip().lower()
if reply in ['y', 'yes']:
install_qemu = True
print("将在安装ESP-IDF后安装QEMU模拟器。")
else:
print("跳过QEMU模拟器安装。")
# 获取远端版本列表
page = 1
per_page = 10
selected_version = None
while selected_version is None:
print()
print("正在获取ESP-IDF版本列表...")
# 获取所有版本
total_versions = get_remote_versions(1000) # 获取足够多的版本
total_pages = (len(total_versions) + per_page - 1) // per_page
if not total_versions:
print("无法获取版本列表")
sys.exit(1)
# 显示当前页的版本(带编号)
start_index = (page - 1) * per_page
end_index = min(page * per_page, len(total_versions))
versions = total_versions[start_index:end_index]
print(f"可用的ESP-IDF版本 (第 {page} 页,共 {total_pages} 页):")
for i, version in enumerate(versions, 1):
print(f"{i:2d}. {version}")
# 如果总页数大于1,显示翻页提示
if total_pages > 1:
print()
print("导航: [P]上一页 [N]下一页 [数字]指定页码")
print(f"选择: 输入版本编号(1-{len(versions)}) 或使用导航命令")
choice = input("请选择操作: ").strip()
if choice.lower() == 'p':
if page > 1:
page -= 1
else:
print("已经是第一页")
input("按回车键继续...")
continue
elif choice.lower() == 'n':
if page < total_pages:
page += 1
else:
print("已经是最后一页")
input("按回车键继续...")
continue
elif choice.isdigit():
choice_num = int(choice)
if 1 <= choice_num <= len(versions):
selected_version = versions[choice_num - 1]
elif 1 <= choice_num <= total_pages:
page = choice_num
continue
else:
print("输入超出范围")
input("按回车键继续...")
continue
else:
print("无效选择")
input("按回车键继续...")
continue
else:
# 只有一页时直接让用户选择版本
print()
print(f"选择: 输入版本编号(1-{len(versions)})")
choice = input("请选择要安装的版本(默认为最新版本): ").strip()
if not choice:
# 选择最新的稳定版本(第一个)
selected_version = versions[0] if versions else None
break
elif choice.isdigit() and 1 <= int(choice) <= len(versions):
selected_version = versions[int(choice) - 1]
break
else:
print("无效选择,将使用默认版本。")
selected_version = versions[0] if versions else None
# 不需要输入回车键继续,直接跳出循环
break
print()
print(f"将要安装的版本: {selected_version}")
# 确认安装
print()
reply = input("确认安装?[Y/n]: ").strip().lower()
if reply in ['n', 'no']:
print("安装已取消。")
sys.exit(0)
# 安装ESP-IDF和QEMU(如果需要)
install_esp_idf(selected_version, install_qemu)
def main():
"""主程序逻辑"""
parser = argparse.ArgumentParser(description="ESP-IDF自动安装脚本")
parser.add_argument("--version", help="指定要安装的ESP-IDF版本(git tag)")
parser.add_argument("--list-versions", action="store_true", help="列出可用的ESP-IDF版本")
parser.add_argument("--version-count", type=int, default=DEFAULT_VERSION_COUNT,
help="指定列出的版本数量(默认:10)")
parser.add_argument("--install-qemu", action="store_true", help="在安装ESP-IDF后安装QEMU模拟器")
args = parser.parse_args()
# 如果只需要列出版本列表
if args.list_versions:
versions = get_remote_versions(args.version_count)
for version in versions:
print(version)
sys.exit(0)
# 如果指定了版本参数,直接安装
if args.version:
install_esp_idf(args.version, args.install_qemu)
else:
# 否则进入交互式模式
interactive_mode()
if __name__ == "__main__":
main()