
Gcc
- 416 installs
- 534 repo stars
- Updated June 29, 2026
- zhinkgit/embeddedskills
gcc is an agent skill that scans, configures, builds, and memory-analyzes CMake ARM embedded projects using arm-none-eabi-gcc.
About
gcc is an agent skill for solo builders and indie hardware teams shipping on ARM microcontrollers. It targets CMake-first embedded repositories that use the Arm GNU toolchain (arm-none-eabi-gcc) and optionally Ninja or Make as the backend generator. The skill discovers projects under a workspace, lists presets from CMakePresets.json, and drives configure and build steps so your agent does not hand-type fragile cmake invocations. After a successful link, it analyzes the ELF for flash and RAM budgeting—text, data, and bss—so you catch size regressions before flashing. Configuration splits between skill-level config.json (cmake path, toolchain prefix, bin directory, safety mode) and per-workspace .embeddedskills/config.json for default project, preset, and log directory. Python orchestration uses only the standard library. When you need on-device debug, documented outputs align with downstream jlink and openocd skills in the same embeddedskills family.
- Scans workspace for CMake embedded projects (not raw Makefile-only trees)
- Enumerates CMakePresets.json configure and build presets
- Runs configure, build, rebuild, and clean via CMake
- Reports ELF text, data, bss, and memory footprint with arm-none-eabi-size
- Returns elf_file, flash_file, debug_file, and log_file paths for jlink or openocd follow-up
Gcc by the numbers
- 416 all-time installs (skills.sh)
- +26 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #129 of 550 CLI & Terminal skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zhinkgit/embeddedskills --skill gccAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 416 |
|---|---|
| repo stars | ★ 534 |
| Security audit | 3 / 3 scanners passed |
| Last updated | June 29, 2026 |
| Repository | zhinkgit/embeddedskills ↗ |
What it does
Scan, configure, build, and size-analyze CMake-based ARM embedded firmware projects with arm-none-eabi-gcc from your coding agent.
Who is it for?
Best when you use CMakePresets with arm-none-eabi-gcc and want repeatable agent-driven builds and size checks.
Skip if: Pure Makefile firmware trees, non-ARM hosts-only apps, or teams that never use CMake for embedded.
When should I use this skill?
User asks to build, configure, rebuild, clean, or size-analyze an embedded CMake ARM GCC project or to list CMake presets.
What you get
You get configured builds, size reports, and artifact paths ready for jlink or openocd flashing and debug.
- Built ELF and related flash/debug artifact paths
- Build logs under configured log_dir
- ELF memory section size summary
By the numbers
- CMake 3.21+ required
- 3 operation_mode safety levels (direct, risk summary, confirm)
- CMake-only scope (no pure Makefile projects)
Files
GCC 嵌入式工程构建
本 skill 提供基于 CMake + arm-none-eabi-gcc 的嵌入式工程发现、preset 枚举、配置生成、增量编译、全量重建、清理和 ELF 大小分析能力。
范围说明:当前仅支持 CMake 型 GCC 嵌入式工程,不覆盖纯 Makefile 工程。
配置
环境级配置(skill/config.json)
skill 目录下的 config.json 包含环境级配置,首次使用前确认 cmake_exe 路径正确:
{
"cmake_exe": "cmake",
"toolchain_prefix": "arm-none-eabi-",
"toolchain_path": "",
"operation_mode": 1
}cmake_exe:cmake 可执行文件路径,默认从 PATH 查找toolchain_prefix:工具链前缀,默认arm-none-eabi-,用于定位 size 等工具toolchain_path:工具链 bin 目录,为空时从 PATH 查找operation_mode:1直接执行 /2输出风险摘要但不阻塞 /3执行前确认
工程级配置(workspace/.embeddedskills/config.json)
工程级共享配置统一保存在工作区的 .embeddedskills/config.json 中:
{
"gcc": {
"project": "",
"preset": "",
"log_dir": ".embeddedskills/build"
}
}project:默认工程路径(相对 workspace),构建成功后会自动更新preset:默认 CMake preset 名称,构建成功后会自动更新log_dir:构建日志输出目录,默认.embeddedskills/build
参数解析优先级
参数解析顺序(从高到低): 1. CLI 显式参数 2. 环境级配置(skill/config.json) 3. 工程级配置(.embeddedskills/config.json) 4. state.json(上次构建记录) 5. 搜索/询问
冲突解决规则:同一参数存在多个来源时,以序号最小的来源为准;高序号来源仅在低序号来源未提供该参数时生效。例如:CLI 已指定 --preset Debug,则忽略 state.json 中记录的上次 preset。
子命令
| 子命令 | 用途 | 风险 |
|---|---|---|
scan | 搜索当前目录下的 CMake 嵌入式工程 | 低 |
presets | 列出 CMakePresets.json 中的 configure/build preset | 低 |
configure | 执行 cmake --preset 生成构建系统 | 中 |
build | 增量编译 cmake --build | 中 |
rebuild | 清理后全量重建 | 中 |
clean | 清理构建目录 | 高 |
size | 分析 ELF 文件大小(text/data/bss 和内存使用) | 低 |
执行流程
1. 读取 config.json,确认 cmake_exe 路径有效 2. 未提供有效子命令时默认执行 scan 3. 未提供工程路径时先执行 scan 搜索工程 4. 发现多个工程或多个 preset 时列出选项让用户选择,绝不自动猜测 5. configure/build/rebuild/clean 按 operation_mode 决定是否需要确认 6. build 前自动检测是否已 configure,未配置时提示先执行 configure 7. build/rebuild 成功后返回 elf_file,供 jlink/openocd 继续使用 8. size 默认分析最近一次构建产物的 .elf 文件
脚本调用
skill 目录下有三个 Python 脚本,使用标准库实现,无额外依赖。
gcc_project.py — 工程扫描与 preset 枚举
# 扫描工程
python <skill-dir>/scripts/gcc_project.py scan --root <搜索目录> --json
# 列出 preset
python <skill-dir>/scripts/gcc_project.py presets --project <工程目录> --jsongcc_build.py — 配置 / 编译 / 重建 / 清理
python <skill-dir>/scripts/gcc_build.py <configure|build|rebuild|clean> \
--cmake <cmake路径> \
--project <工程根目录> \
--preset <preset名称> \
--log-dir <日志目录> \
--jsongcc_size.py — ELF 大小分析
# 基本分析
python <skill-dir>/scripts/gcc_size.py analyze \
--elf <elf文件路径> \
--toolchain-prefix arm-none-eabi- \
--linker-script <链接脚本路径> \
--json
# 对比分析
python <skill-dir>/scripts/gcc_size.py compare \
--elf <elf文件1> \
--compare <elf文件2> \
--toolchain-prefix arm-none-eabi- \
--json输出格式
所有脚本以 JSON 格式返回,基础字段为 status(ok/error)、action、summary、details,并可能附带 context、artifacts、metrics、state、next_actions、timing。
成功示例:
{
"status": "ok",
"action": "build",
"summary": "build 成功,errors=0 warnings=2",
"details": { "project": "...", "preset": "Debug", "build_dir": "...", "elf_file": "...", "log_file": "..." },
"metrics": { "errors": 0, "warnings": 2, "flash_bytes": 99328, "ram_bytes": 46080 }
}错误示例:
{
"status": "error",
"action": "build",
"error": { "code": "not_configured", "message": "构建目录不存在,请先执行 configure" }
}核心规则
- 不修改 CMakeLists.txt 或任何 CMake 配置文件
- 当前 skill 仅覆盖 CMake 型 GCC 工程,不对纯 Makefile 工程做识别和构建
- 不自动猜测工程路径或 preset,有歧义时必须询问用户
- 参数解析优先级为:CLI 显式参数 > 环境级配置 > 工程级配置 >
.embeddedskills/state.json> 搜索/询问 clean不在自动流程中隐式执行- 构建失败时优先展示首个错误和日志文件路径
- 结果回显中始终包含工程名、preset 名、构建目录路径;构建成功时优先回显
elf_file
{
"cmake_exe": "cmake",
"toolchain_prefix": "arm-none-eabi-",
"toolchain_path": "",
"operation_mode": 1
}
gcc
Claude Code skill,用于基于 CMake + arm-none-eabi-gcc 的嵌入式工程扫描、preset 枚举、配置、构建和 ELF 大小分析。
范围说明:当前仅支持 CMake 型 嵌入式 GCC 工程,不覆盖纯 Makefile 工程。
功能
- 扫描目录下的嵌入式 CMake 工程
- 枚举
CMakePresets.json中的 configure/build preset - 执行
configure/build/rebuild/clean - 分析 ELF 的
text/data/bss和内存占用 - 返回
elf_file/flash_file/debug_file/log_file等产物路径,便于继续交给jlink/openocd
环境要求
- CMake 3.21 或更高版本
- Ninja(推荐)或 Make
- Arm GNU Toolchain(提供
arm-none-eabi-gcc、arm-none-eabi-size等) - Python 3.x(仅标准库,无额外依赖)
配置
环境级配置(skill/config.json)
复制 config.example.json 为 config.json,根据实际环境修改:
{
"cmake_exe": "cmake",
"toolchain_prefix": "arm-none-eabi-",
"toolchain_path": "",
"operation_mode": 1
}| 字段 | 必填 | 说明 |
|---|---|---|
cmake_exe | 否 | cmake 路径或命令名,默认从 PATH 查找 |
toolchain_prefix | 否 | 工具链前缀,默认 arm-none-eabi- |
toolchain_path | 否 | 工具链 bin 目录,为空时从 PATH 查找 |
operation_mode | 否 | 1 直接执行 / 2 输出风险摘要 / 3 执行前确认 |
工程级配置(workspace/.embeddedskills/config.json)
工程级共享配置保存在工作区的 .embeddedskills/config.json 中:
{
"gcc": {
"project": "",
"preset": "",
"log_dir": ".embeddedskills/build"
}
}| 字段 | 说明 |
|---|---|
project | 默认工程路径(相对 workspace) |
preset | 默认 CMake preset 名称 |
log_dir | 构建日志输出目录,默认 .embeddedskills/build |
参数解析优先级
参数解析顺序(从高到低): 1. CLI 显式参数 2. 环境级配置(skill/config.json) 3. 工程级配置(.embeddedskills/config.json) 4. state.json(上次构建记录) 5. 搜索/询问
子命令
| 子命令 | 用途 |
|---|---|
scan | 搜索嵌入式 CMake 工程 |
presets | 列出 CMake preset |
configure | 生成构建系统 |
build | 增量编译 |
rebuild | 全量重建 |
clean | 清理构建目录 |
size | 分析 ELF 大小 |
使用说明
build前若尚未完成configure,脚本会提示先执行configure- 发现多个工程或多个 preset 时,只返回候选项,不自动猜测
build/rebuild成功后会返回elf_file,同时复用为flash_file和debug_filesize默认分析最近一次构建产物;底层gcc_size.py额外支持两个 ELF 的对比分析clean不会在自动流程中隐式执行
"""GCC 嵌入式工程构建:configure / build / rebuild / clean。"""
from __future__ import annotations
import argparse
import json
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
ROOT_DIR = Path(__file__).resolve().parents[2]
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from gcc_runtime import ( # noqa: E402
build_artifacts,
default_config_path,
get_state_entry,
hidden_subprocess_kwargs,
is_missing,
load_json_file,
load_local_config,
load_project_config,
load_workspace_state,
make_result,
make_timing,
normalize_path,
now_iso,
output_json,
parameter_context,
resolve_param,
save_project_config,
update_state_entry,
workspace_root,
)
def _resolve_build_dir(project: Path, preset: str, presets_file: Path) -> Path:
if presets_file.exists():
data = json.loads(presets_file.read_text(encoding="utf-8"))
all_presets = {item["name"]: item for item in data.get("configurePresets", [])}
preset_item = all_presets.get(preset, {})
binary_dir = preset_item.get("binaryDir", "")
if not binary_dir:
inherits = preset_item.get("inherits")
if inherits and isinstance(inherits, str):
binary_dir = all_presets.get(inherits, {}).get("binaryDir", "")
if binary_dir:
binary_dir = binary_dir.replace("${sourceDir}", str(project))
binary_dir = binary_dir.replace("${presetName}", preset)
return Path(binary_dir)
return project / "build" / preset
def _resolve_workspace_path(workspace: Path, raw_path: str | None, default: str) -> str:
value = default if is_missing(raw_path) else str(raw_path)
path = Path(value)
return str(path.resolve() if path.is_absolute() else (workspace / path).resolve())
def _resolve_project_path(workspace: Path, raw_path: str | None) -> str:
if is_missing(raw_path):
return ""
path = Path(str(raw_path)).expanduser()
return str(path.resolve() if path.is_absolute() else (workspace / path).resolve())
def _make_relative_to_workspace(workspace: Path, path: str) -> str:
"""将绝对路径转换为相对于 workspace 的相对路径"""
try:
p = Path(path).resolve()
ws = workspace.resolve()
rel = p.relative_to(ws)
return str(rel).replace("\\", "/")
except ValueError:
return path
def _find_elf(build_dir: Path, project_name: str) -> str:
elfs = list(build_dir.glob("*.elf"))
if not elfs:
elfs = list(build_dir.rglob("*.elf"))
if not elfs:
return ""
for file_path in elfs:
if file_path.stem.lower() == project_name.lower():
return str(file_path.resolve())
return str(elfs[0].resolve())
def _parse_build_output(output: str) -> dict:
metrics = {"errors": 0, "warnings": 0, "flash_bytes": 0, "ram_bytes": 0}
metrics["errors"] = len(re.findall(r":\d+:\d+:\s+error:", output))
metrics["warnings"] = len(re.findall(r":\d+:\d+:\s+warning:", output))
for match in re.finditer(r"(FLASH|RAM|CCMRAM)\s*:\s*([\d]+)\s*(B|KB|MB)", output, re.IGNORECASE):
region = match.group(1).upper()
value = int(match.group(2))
unit = match.group(3).upper()
if unit == "KB":
value *= 1024
elif unit == "MB":
value *= 1024 * 1024
if region == "FLASH":
metrics["flash_bytes"] = value
elif region == "RAM":
metrics["ram_bytes"] = value
return metrics
def _extract_first_error(output: str) -> str:
for line in output.splitlines():
if re.search(r":\d+:\d+:\s+error:", line):
return re.sub(r"^\[\d+/\d+\]\s*", "", line).strip()
return ""
def _error(action: str, code: str, message: str, details: dict | None = None) -> dict:
return {
"status": "error",
"action": action,
"error": {"code": code, "message": message},
"details": details or {},
}
def _build_summary(action: str, status: str, metrics: dict | None = None) -> str:
metrics = metrics or {}
if action in ("build", "rebuild"):
return (
f"{action} {'成功' if status == 'ok' else '失败'},"
f"errors={metrics.get('errors', 0)} warnings={metrics.get('warnings', 0)}"
)
if action == "configure":
return "configure 成功" if status == "ok" else "configure 失败"
return "clean 成功" if status == "ok" else "clean 失败"
TARGET_CLEAN_TIMEOUT_SECONDS = 5
def _terminate_process_tree(proc: subprocess.Popen) -> None:
if proc.poll() is not None:
return
if sys.platform == "win32":
try:
subprocess.run(
["taskkill", "/PID", str(proc.pid), "/T", "/F"],
capture_output=True,
text=True,
timeout=1,
encoding="utf-8",
errors="replace",
**hidden_subprocess_kwargs(),
)
except (subprocess.SubprocessError, FileNotFoundError):
proc.kill()
else:
proc.kill()
try:
proc.wait(timeout=1)
except subprocess.TimeoutExpired:
proc.kill()
def run_configure(cmake_exe: str, project: str, preset: str, log_dir: str) -> dict:
project_path = Path(project).resolve()
if not (project_path / "CMakeLists.txt").exists():
return _error("configure", "project_not_found", f"CMakeLists.txt 不存在: {project_path}")
log_path = Path(log_dir).resolve()
log_path.mkdir(parents=True, exist_ok=True)
log_file = log_path / f"{project_path.name}-{preset}-configure.log"
try:
proc = subprocess.run(
[cmake_exe, "--preset", preset],
capture_output=True,
text=True,
timeout=300,
cwd=str(project_path),
encoding="utf-8",
errors="replace",
**hidden_subprocess_kwargs(),
)
except subprocess.TimeoutExpired:
return _error("configure", "timeout", "cmake 配置超时(300s)")
except FileNotFoundError:
return _error("configure", "cmake_not_found", f"cmake 不存在: {cmake_exe}")
output = proc.stdout + "\n" + proc.stderr
log_file.write_text(output, encoding="utf-8")
if proc.returncode != 0:
return _error(
"configure",
"configure_failed",
output.strip()[-500:] or f"cmake configure 返回码: {proc.returncode}",
{"project": str(project_path), "preset": preset, "log_file": str(log_file.resolve())},
)
return {
"status": "ok",
"action": "configure",
"details": {
"project": str(project_path),
"preset": preset,
"log_file": str(log_file.resolve()),
},
}
def run_build(cmake_exe: str, project: str, preset: str, log_dir: str) -> dict:
project_path = Path(project).resolve()
build_dir = _resolve_build_dir(project_path, preset, project_path / "CMakePresets.json")
if not (build_dir / "build.ninja").exists() and not (build_dir / "Makefile").exists():
return _error(
"build",
"not_configured",
f"构建目录不存在或未配置: {build_dir},请先执行 configure",
{"project": str(project_path), "preset": preset, "build_dir": str(build_dir.resolve())},
)
log_path = Path(log_dir).resolve()
log_path.mkdir(parents=True, exist_ok=True)
log_file = log_path / f"{project_path.name}-{preset}-build.log"
try:
proc = subprocess.run(
[cmake_exe, "--build", str(build_dir)],
capture_output=True,
text=True,
timeout=600,
cwd=str(project_path),
encoding="utf-8",
errors="replace",
**hidden_subprocess_kwargs(),
)
except subprocess.TimeoutExpired:
return _error("build", "timeout", "构建超时(600s)")
except FileNotFoundError:
return _error("build", "cmake_not_found", f"cmake 不存在: {cmake_exe}")
output = proc.stdout + "\n" + proc.stderr
log_file.write_text(output, encoding="utf-8")
metrics = _parse_build_output(output)
details = {
"project": str(project_path),
"preset": preset,
"build_dir": str(build_dir.resolve()),
"log_file": str(log_file.resolve()),
}
if proc.returncode != 0 or metrics["errors"] > 0:
return {
"status": "error",
"action": "build",
"metrics": metrics,
"error": {
"code": "build_failed",
"message": _extract_first_error(output) or f"构建失败,返回码: {proc.returncode}",
},
"details": details,
}
elf_file = _find_elf(build_dir, project_path.name)
if elf_file:
details["elf_file"] = elf_file
details["debug_file"] = elf_file
details["flash_file"] = elf_file
return {
"status": "ok",
"action": "build",
"metrics": metrics,
"details": details,
}
def run_clean(cmake_exe: str, project: str, preset: str, log_dir: str) -> dict:
project_path = Path(project).resolve()
build_dir = _resolve_build_dir(project_path, preset, project_path / "CMakePresets.json")
fallback_reason = ""
fallback_output = ""
if not build_dir.exists():
return {
"status": "ok",
"action": "clean",
"details": {
"project": str(project_path),
"preset": preset,
"build_dir": str(build_dir.resolve()),
"log_dir": str(Path(log_dir).resolve()),
},
}
if (build_dir / "build.ninja").exists() or (build_dir / "Makefile").exists():
try:
proc = subprocess.Popen(
[cmake_exe, "--build", str(build_dir), "--target", "clean"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
errors="replace",
**hidden_subprocess_kwargs(),
)
try:
stdout, stderr = proc.communicate(timeout=TARGET_CLEAN_TIMEOUT_SECONDS)
except subprocess.TimeoutExpired:
_terminate_process_tree(proc)
fallback_reason = f"target clean 超时({TARGET_CLEAN_TIMEOUT_SECONDS}s)"
fallback_output = ""
else:
if proc.returncode == 0:
return {
"status": "ok",
"action": "clean",
"details": {
"project": str(project_path),
"preset": preset,
"build_dir": str(build_dir.resolve()),
"mode": "target-clean",
},
}
fallback_reason = f"target clean 返回码 {proc.returncode}"
fallback_output = (stdout or "") + ("\n" if stdout and stderr else "") + (stderr or "")
except FileNotFoundError:
fallback_reason = "target clean 无法执行"
try:
shutil.rmtree(str(build_dir))
except OSError as exc:
return _error("clean", "clean_failed", f"删除构建目录失败: {exc}")
return {
"status": "ok",
"action": "clean",
"details": {
"project": str(project_path),
"preset": preset,
"build_dir": str(build_dir.resolve()),
"mode": "remove-tree",
"fallback_reason": fallback_reason,
"fallback_output": fallback_output.strip()[-500:] if fallback_output else "",
},
}
def run_rebuild(cmake_exe: str, project: str, preset: str, log_dir: str) -> dict:
result = run_clean(cmake_exe, project, preset, log_dir)
if result["status"] == "error":
result["action"] = "rebuild"
return result
result = run_configure(cmake_exe, project, preset, log_dir)
if result["status"] == "error":
result["action"] = "rebuild"
return result
result = run_build(cmake_exe, project, preset, log_dir)
result["action"] = "rebuild"
return result
def main() -> None:
parser = argparse.ArgumentParser(description="GCC 嵌入式工程构建")
parser.add_argument("action", choices=["configure", "build", "rebuild", "clean"])
parser.add_argument("--cmake", default=None, help="cmake 可执行文件路径")
parser.add_argument("--project", default=None, help="工程根目录")
parser.add_argument("--preset", default=None, help="CMake preset 名称")
parser.add_argument("--log-dir", default=None, help="日志输出目录")
parser.add_argument("--config", default=None, help="skill config.json 路径")
parser.add_argument("--workspace", default=None, help="workspace 根目录,默认当前目录")
parser.add_argument("--json", action="store_true", dest="as_json")
args = parser.parse_args()
started_at = now_iso()
started_ts = time.time()
workspace = workspace_root(args.workspace)
# 加载三层配置:环境级、工程级、状态
local_config = load_local_config(__file__)
project_config = load_project_config(str(workspace))
state = load_workspace_state(str(workspace))
last_build = get_state_entry(state, "last_build")
parameter_sources: dict[str, str] = {}
try:
# cmake_exe: CLI > 环境级配置 > PATH 默认值(cmake)
cmake_exe, parameter_sources["cmake"] = resolve_param(
"cmake",
args.cmake,
config=local_config,
config_keys=["cmake_exe"],
)
if is_missing(cmake_exe):
cmake_exe = "cmake"
parameter_sources["cmake"] = "default:path:cmake"
# project: CLI > 环境级配置 > 工程级配置 > state.json > 必需
project, parameter_sources["project"] = resolve_param(
"project",
args.project,
config=local_config,
config_keys=["default_project"],
)
if not is_missing(project):
project = _resolve_project_path(workspace, str(project))
# 工程级配置(优先于 state)
if is_missing(project) and not is_missing(project_config.get("project")):
project = _resolve_project_path(workspace, project_config.get("project"))
parameter_sources["project"] = "project_config:project"
# state.json(最后 fallback)
if is_missing(project) and not is_missing(last_build.get("project")):
project = _resolve_project_path(workspace, str(last_build.get("project")))
parameter_sources["project"] = "state:project"
if is_missing(project):
raise ValueError("缺少必要参数: project")
# preset: CLI > 环境级配置 > 工程级配置 > state.json > 必需
preset, parameter_sources["preset"] = resolve_param(
"preset",
args.preset,
config=local_config,
config_keys=["default_preset"],
)
# 工程级配置(优先于 state)
if is_missing(preset) and not is_missing(project_config.get("preset")):
preset = project_config.get("preset")
parameter_sources["preset"] = "project_config:preset"
# state.json(最后 fallback)
if is_missing(preset) and not is_missing(last_build.get("preset")):
preset = last_build.get("preset")
parameter_sources["preset"] = "state:preset"
if is_missing(preset):
raise ValueError("缺少必要参数: preset")
# log_dir: CLI > 工程级配置 > 环境级配置 > 默认值(.embeddedskills/build)
log_dir_raw = args.log_dir or project_config.get("log_dir") or local_config.get("log_dir")
log_dir = _resolve_workspace_path(workspace, log_dir_raw, ".embeddedskills/build")
if args.log_dir:
parameter_sources["log_dir"] = "cli"
elif project_config.get("log_dir"):
parameter_sources["log_dir"] = "project_config:log_dir"
elif local_config.get("log_dir"):
parameter_sources["log_dir"] = "config:log_dir"
else:
parameter_sources["log_dir"] = "default"
except ValueError as exc:
result = make_result(
status="error",
action=args.action,
summary=str(exc),
details={},
context=parameter_context(
provider="gcc",
workspace=str(workspace),
parameter_sources=parameter_sources,
),
error={"code": "missing_param", "message": str(exc)},
timing=make_timing(started_at, (time.time() - started_ts) * 1000),
)
if args.as_json:
output_json(result)
else:
print(f"错误: {exc}", file=sys.stderr)
sys.exit(1)
action_map = {
"configure": run_configure,
"build": run_build,
"rebuild": run_rebuild,
"clean": run_clean,
}
raw_result = action_map[args.action](cmake_exe=cmake_exe, project=project, preset=preset, log_dir=log_dir)
elapsed_ms = (time.time() - started_ts) * 1000
if raw_result["status"] == "error":
result = make_result(
status="error",
action=raw_result["action"],
summary=raw_result["error"]["message"],
details=raw_result.get("details", {}),
context=parameter_context(
provider="gcc",
workspace=str(workspace),
parameter_sources=parameter_sources,
),
metrics=raw_result.get("metrics", {}),
error=raw_result["error"],
timing=make_timing(started_at, elapsed_ms),
)
else:
details = raw_result.get("details", {})
artifacts = build_artifacts(
build_dir=details.get("build_dir"),
elf_file=details.get("elf_file"),
debug_file=details.get("debug_file"),
flash_file=details.get("flash_file"),
log_file=details.get("log_file"),
)
metrics = raw_result.get("metrics", {})
state_info = None
if args.action in ("build", "rebuild") and raw_result["status"] == "ok":
state_info = update_state_entry(
"last_build",
{
"provider": "gcc",
"action": args.action,
"project": project,
"preset": preset,
"log_dir": log_dir,
"artifacts": artifacts,
**artifacts,
},
str(workspace),
)
summary = _build_summary(args.action, raw_result["status"], metrics)
next_actions = []
if artifacts.get("flash_file"):
next_actions.append("可直接复用 artifacts.flash_file 继续 flash")
if artifacts.get("debug_file"):
next_actions.append("可直接复用 artifacts.debug_file 继续 gdb 调试")
# 构建成功后,将确认过的参数写回工程级配置
if raw_result["status"] == "ok":
project_rel = _make_relative_to_workspace(workspace, project)
save_project_config(
str(workspace),
{
"project": project_rel,
"preset": preset or "",
"log_dir": _make_relative_to_workspace(workspace, log_dir),
},
)
result = make_result(
status=raw_result["status"],
action=raw_result["action"],
summary=summary,
details=details,
context=parameter_context(
provider="gcc",
workspace=str(workspace),
parameter_sources=parameter_sources,
),
artifacts=artifacts,
metrics=metrics,
state=state_info,
next_actions=next_actions,
timing=make_timing(started_at, elapsed_ms),
)
if args.as_json:
output_json(result)
return
if result["status"] == "ok":
print(f"[{args.action}] {result['summary']}")
if result.get("artifacts", {}).get("log_file"):
print(f" 日志: {result['artifacts']['log_file']}")
if result.get("artifacts", {}).get("elf_file"):
print(f" ELF: {result['artifacts']['elf_file']}")
else:
error = result.get("error", {})
print(f"[{args.action}] 失败 — {error.get('message', result['summary'])}", file=sys.stderr)
if result.get("details", {}).get("log_file"):
print(f" 日志: {result['details']['log_file']}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
"""GCC 嵌入式工程扫描与 CMake preset 枚举"""
import argparse
import json
import re
import sys
from pathlib import Path
EXCLUDE_DIRS = {"build", ".git", "node_modules", "__pycache__", ".vscode"}
def scan_projects(root: str) -> list[dict]:
"""递归搜索含 CMakeLists.txt 的嵌入式 CMake 工程"""
root_path = Path(root).resolve()
projects = []
for cmake_file in root_path.rglob("CMakeLists.txt"):
# 排除构建目录等
if any(part in EXCLUDE_DIRS for part in cmake_file.parts):
continue
proj_dir = cmake_file.parent
# 检查嵌入式特征:CMakePresets.json 或 cmake/ 下含工具链文件
has_presets = (proj_dir / "CMakePresets.json").exists()
has_toolchain = _has_embedded_toolchain(proj_dir)
if not has_presets and not has_toolchain:
continue
# 提取项目名
name = _extract_project_name(cmake_file) or proj_dir.name
projects.append({
"path": str(proj_dir),
"name": name,
"has_presets": has_presets,
})
projects.sort(key=lambda x: x["path"])
return projects
def _has_embedded_toolchain(proj_dir: Path) -> bool:
"""检查是否有嵌入式工具链文件"""
cmake_dir = proj_dir / "cmake"
if not cmake_dir.is_dir():
return False
for f in cmake_dir.iterdir():
if f.suffix == ".cmake" and f.is_file():
try:
content = f.read_text(encoding="utf-8", errors="replace").lower()
if "arm-none-eabi" in content or "cross" in content:
return True
except OSError:
pass
return False
def _extract_project_name(cmake_file: Path) -> str:
"""从 CMakeLists.txt 提取 project(NAME) 中的名称"""
try:
content = cmake_file.read_text(encoding="utf-8", errors="replace")
m = re.search(r"project\s*\(\s*(\w+)", content, re.IGNORECASE)
if m:
return m.group(1)
except OSError:
pass
return ""
def list_presets(project_dir: str) -> dict:
"""读取 CMakePresets.json,列出 configure 和 build preset"""
proj_path = Path(project_dir).resolve()
presets_file = proj_path / "CMakePresets.json"
if not presets_file.exists():
raise FileNotFoundError(f"CMakePresets.json 不存在: {presets_file}")
data = json.loads(presets_file.read_text(encoding="utf-8"))
# 合并 CMakeUserPresets.json(如果存在)
user_presets_file = proj_path / "CMakeUserPresets.json"
if user_presets_file.exists():
user_data = json.loads(user_presets_file.read_text(encoding="utf-8"))
data.setdefault("configurePresets", []).extend(
user_data.get("configurePresets", [])
)
data.setdefault("buildPresets", []).extend(
user_data.get("buildPresets", [])
)
source_dir = str(proj_path)
all_presets = {cp["name"]: cp for cp in data.get("configurePresets", [])}
def _resolve_inherited(preset: dict, field: str) -> str:
"""沿 inherits 链查找字段值"""
val = preset.get(field, "")
if val:
return val
inherits = preset.get("inherits")
if inherits and isinstance(inherits, str):
parent = all_presets.get(inherits)
if parent:
return _resolve_inherited(parent, field)
return ""
def _resolve_cache_vars(preset: dict) -> dict:
"""沿 inherits 链合并 cacheVariables"""
cache_vars = dict(preset.get("cacheVariables", {}))
inherits = preset.get("inherits")
if inherits and isinstance(inherits, str):
parent = all_presets.get(inherits)
if parent:
parent_vars = _resolve_cache_vars(parent)
parent_vars.update(cache_vars)
cache_vars = parent_vars
return cache_vars
configure_presets = []
for p in data.get("configurePresets", []):
if p.get("hidden", False):
continue
binary_dir = _resolve_inherited(p, "binaryDir")
binary_dir = binary_dir.replace("${sourceDir}", source_dir)
binary_dir = binary_dir.replace("${presetName}", p["name"])
generator = _resolve_inherited(p, "generator")
cache_vars = _resolve_cache_vars(p)
configure_presets.append({
"name": p["name"],
"build_type": cache_vars.get("CMAKE_BUILD_TYPE", ""),
"generator": generator,
"binary_dir": binary_dir,
})
build_presets = []
for p in data.get("buildPresets", []):
if p.get("hidden", False):
continue
build_presets.append({
"name": p["name"],
"configure_preset": p.get("configurePreset", ""),
})
return {
"project": str(proj_path),
"configure_presets": configure_presets,
"build_presets": build_presets,
}
def output_json(data: dict):
sys.stdout.reconfigure(encoding="utf-8")
print(json.dumps(data, ensure_ascii=False, indent=2))
def main():
parser = argparse.ArgumentParser(description="GCC 嵌入式工程扫描与 preset 枚举")
sub = parser.add_subparsers(dest="command")
scan_p = sub.add_parser("scan", help="搜索嵌入式 CMake 工程")
scan_p.add_argument("--root", default=".", help="搜索根目录")
scan_p.add_argument("--json", action="store_true", dest="as_json")
presets_p = sub.add_parser("presets", help="列出 CMake preset")
presets_p.add_argument("--project", required=True, help="工程目录路径")
presets_p.add_argument("--json", action="store_true", dest="as_json")
args = parser.parse_args()
if args.command == "scan":
projects = scan_projects(args.root)
result = {
"status": "ok",
"action": "scan",
"details": {"projects": projects, "count": len(projects)},
}
if args.as_json:
output_json(result)
else:
if not projects:
print("未找到嵌入式 CMake 工程")
else:
print(f"找到 {len(projects)} 个工程:")
for p in projects:
preset_tag = " [presets]" if p["has_presets"] else ""
print(f" {p['name']}{preset_tag} — {p['path']}")
elif args.command == "presets":
try:
details = list_presets(args.project)
result = {
"status": "ok",
"action": "presets",
"details": details,
}
if args.as_json:
output_json(result)
else:
print(f"工程: {details['project']}")
print("Configure presets:")
for p in details["configure_presets"]:
print(f" - {p['name']} ({p['build_type']}) -> {p['binary_dir']}")
if details["build_presets"]:
print("Build presets:")
for p in details["build_presets"]:
print(f" - {p['name']} (configure: {p['configure_preset']})")
except (FileNotFoundError, ValueError) as e:
result = {
"status": "error",
"action": "presets",
"error": {"code": "invalid_project", "message": str(e)},
}
if args.as_json:
output_json(result)
else:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()
"""gcc skill 私有运行时工具。"""
from __future__ import annotations
import json
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
STATE_DIR_NAME = ".embeddedskills"
STATE_FILE_NAME = "state.json"
PROJECT_CONFIG_FILE_NAME = "config.json"
# Skill name for project config
SKILL_NAME = "gcc"
def now_iso() -> str:
return datetime.now().astimezone().isoformat(timespec="seconds")
def default_config_path(script_file: str) -> Path:
return Path(script_file).resolve().parents[1] / "config.json"
def load_local_config(script_file: str | None = None) -> dict:
"""加载 skill/config.json(环境级配置)
路径:当前脚本所在 skill 目录下的 config.json
"""
if script_file is None:
# 获取调用者的文件路径
import inspect
frame = inspect.currentframe()
if frame and frame.f_back:
script_file = frame.f_back.f_globals.get("__file__", "")
if not script_file:
return {}
config_path = default_config_path(script_file)
return load_json_file(config_path)
def save_local_config(data: dict, script_file: str | None = None) -> Path | None:
"""保存环境级配置到 skill/config.json"""
if script_file is None:
import inspect
frame = inspect.currentframe()
if frame and frame.f_back:
script_file = frame.f_back.f_globals.get("__file__", "")
if not script_file:
return None
config_path = default_config_path(script_file)
existing = load_json_file(config_path)
existing.update(data)
save_json_file(config_path, existing)
return config_path
def load_project_config(workspace: str | None = None) -> dict:
"""从 workspace/.embeddedskills/config.json 读取本 skill 的工程级配置
参数: workspace - 工作区路径,None 时使用 cwd
返回: 该 skill 对应的配置字典(如 config["keil"] 或 config["gcc"])
"""
ws = workspace_root(workspace)
config_file = ws / STATE_DIR_NAME / PROJECT_CONFIG_FILE_NAME
data = load_json_file(config_file)
return data.get(SKILL_NAME, {})
def save_project_config(workspace: str | None = None, values: dict | None = None) -> Path | None:
"""写回工程级配置到 workspace/.embeddedskills/config.json
- 只更新本 skill 的配置部分,不覆盖其他 skill 的配置
- 目录不存在时自动创建 .embeddedskills/
"""
if values is None:
values = {}
ws = workspace_root(workspace)
config_file = ws / STATE_DIR_NAME / PROJECT_CONFIG_FILE_NAME
data = load_json_file(config_file)
data[SKILL_NAME] = {**(data.get(SKILL_NAME, {})), **values}
save_json_file(config_file, data)
return config_file
def output_json(data: dict, *, indent: int = 2) -> None:
sys.stdout.reconfigure(encoding="utf-8")
print(json.dumps(data, ensure_ascii=False, indent=indent), flush=True)
def is_missing(value: Any) -> bool:
return value is None or value == ""
def normalize_path(value: str | None) -> str:
if is_missing(value):
return ""
return str(Path(str(value)).expanduser().resolve())
def load_json_file(path: str | Path) -> dict:
file_path = Path(path)
if not file_path.exists():
return {}
try:
return json.loads(file_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return {}
def save_json_file(path: str | Path, data: dict) -> None:
file_path = Path(path)
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
def hidden_subprocess_kwargs() -> dict:
if sys.platform != "win32":
return {}
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = getattr(subprocess, "SW_HIDE", 0)
return {
"creationflags": getattr(subprocess, "CREATE_NO_WINDOW", 0),
"startupinfo": startupinfo,
}
def workspace_root(workspace: str | None = None) -> Path:
if not is_missing(workspace):
return Path(str(workspace)).expanduser().resolve()
return Path.cwd().resolve()
def load_workspace_state(workspace: str | None = None) -> dict:
return load_json_file(workspace_root(workspace) / STATE_DIR_NAME / STATE_FILE_NAME)
def save_workspace_state(state: dict, workspace: str | None = None) -> Path:
file_path = workspace_root(workspace) / STATE_DIR_NAME / STATE_FILE_NAME
save_json_file(file_path, state)
return file_path
def get_state_entry(state: dict | None, key: str) -> dict:
if not isinstance(state, dict):
return {}
value = state.get(key, {})
return value if isinstance(value, dict) else {}
def update_state_entry(category: str, record: dict, workspace: str | None = None) -> dict:
state = load_workspace_state(workspace)
state[category] = {**record, "timestamp": record.get("timestamp") or now_iso()}
file_path = save_workspace_state(state, workspace)
return {
"workspace": str(workspace_root(workspace)),
"file": str(file_path),
"updated_keys": [category],
category: state[category],
}
def _first_resolved(mapping: dict, keys: list[str]) -> tuple[Any, str | None]:
for key in keys:
value = mapping.get(key)
if not is_missing(value):
return value, key
return None, None
def resolve_param(
name: str,
cli_value: Any,
*,
config: dict | None = None,
config_keys: list[str] | None = None,
state_record: dict | None = None,
state_keys: list[str] | None = None,
required: bool = False,
normalize_as_path: bool = False,
) -> tuple[Any, str]:
if not is_missing(cli_value):
value = cli_value
source = "cli"
else:
value = None
source = ""
if config and config_keys:
value, config_key = _first_resolved(config, config_keys)
if not is_missing(value):
source = f"config:{config_key}"
if is_missing(value) and state_record and state_keys:
value, state_key = _first_resolved(state_record, state_keys)
if not is_missing(value):
source = f"state:{state_key}"
if normalize_as_path and not is_missing(value):
value = normalize_path(str(value))
if required and is_missing(value):
raise ValueError(f"缺少必要参数: {name}")
return value, source
def compact_dict(data: dict | None) -> dict:
if not isinstance(data, dict):
return {}
return {key: value for key, value in data.items() if value not in (None, "", [], {})}
def build_artifacts(**paths: str) -> dict:
return {key: normalize_path(str(value)) for key, value in paths.items() if not is_missing(value)}
def make_result(
*,
status: str,
action: str,
summary: str,
details: dict | None = None,
context: dict | None = None,
artifacts: dict | None = None,
metrics: dict | None = None,
state: dict | None = None,
next_actions: list[str] | None = None,
timing: dict | None = None,
error: dict | None = None,
) -> dict:
result = {"status": status, "action": action, "summary": summary, "details": compact_dict(details)}
optional = {
"context": compact_dict(context),
"artifacts": compact_dict(artifacts),
"metrics": compact_dict(metrics),
"state": compact_dict(state),
"timing": compact_dict(timing),
}
for key, value in optional.items():
if value:
result[key] = value
if next_actions:
result["next_actions"] = [item for item in next_actions if item]
if error:
result["error"] = compact_dict(error)
return result
def make_timing(started_at: str, elapsed_ms: int | float) -> dict:
return {"started_at": started_at, "finished_at": now_iso(), "elapsed_ms": int(elapsed_ms)}
def parameter_context(*, provider: str, workspace: str | None = None, parameter_sources: dict | None = None, config_path: str | None = None) -> dict:
context = {"provider": provider, "workspace": str(workspace_root(workspace))}
if parameter_sources:
context["parameter_sources"] = compact_dict(parameter_sources)
if not is_missing(config_path):
context["config_path"] = normalize_path(str(config_path))
return context
"""GCC 嵌入式 ELF 大小分析"""
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from gcc_runtime import hidden_subprocess_kwargs
def _find_size_tool(toolchain_prefix: str, toolchain_path: str) -> str:
"""拼接 size 工具的完整路径"""
tool_name = f"{toolchain_prefix}size"
if toolchain_path:
return str(Path(toolchain_path) / tool_name)
return tool_name
def _run_size(size_exe: str, elf: str, fmt: str) -> str:
"""调用 arm-none-eabi-size"""
cmd = [size_exe, f"-{fmt}", elf]
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=30,
encoding="utf-8", errors="replace",
**hidden_subprocess_kwargs(),
)
if proc.returncode != 0:
raise RuntimeError(f"size 执行失败: {proc.stderr.strip()}")
return proc.stdout
def _parse_size_berkeley(output: str) -> dict:
"""-B 格式: text data bss dec hex filename"""
lines = output.strip().splitlines()
if len(lines) < 2:
return {}
parts = lines[1].split()
if len(parts) < 4:
return {}
return {
"text": int(parts[0]),
"data": int(parts[1]),
"bss": int(parts[2]),
"total": int(parts[3]),
}
def _parse_size_sysv(output: str) -> list[dict]:
"""-A 格式: section size addr"""
sections = []
for line in output.strip().splitlines():
m = re.match(r"^(\.\S+)\s+(\d+)\s+(0x[0-9a-fA-F]+|\d+)", line)
if m:
sections.append({
"name": m.group(1),
"size": int(m.group(2)),
"addr": m.group(3),
})
return sections
def _parse_linker_script(ld_path: str) -> dict:
"""从链接脚本解析 MEMORY 区域"""
content = Path(ld_path).read_text(encoding="utf-8", errors="replace")
regions = {}
for m in re.finditer(
r"(\w+)\s*\([^)]*\)\s*:\s*ORIGIN\s*=\s*(0x[0-9a-fA-F]+)\s*,\s*LENGTH\s*=\s*(\d+)([KMG]?)",
content, re.IGNORECASE,
):
name = m.group(1).upper()
origin = int(m.group(2), 16)
length = int(m.group(3))
unit = m.group(4).upper()
if unit == "K":
length *= 1024
elif unit == "M":
length *= 1024 * 1024
elif unit == "G":
length *= 1024 * 1024 * 1024
regions[name] = {"origin": origin, "length": length}
return regions
def analyze(elf: str, toolchain_prefix: str, toolchain_path: str,
linker_script: str) -> dict:
"""分析 ELF 文件大小"""
elf_path = Path(elf).resolve()
if not elf_path.exists():
return _error("size", "elf_not_found", f"ELF 文件不存在: {elf_path}")
size_exe = _find_size_tool(toolchain_prefix, toolchain_path)
try:
berkeley_output = _run_size(size_exe, str(elf_path), "B")
sysv_output = _run_size(size_exe, str(elf_path), "A")
except (RuntimeError, FileNotFoundError, subprocess.TimeoutExpired) as e:
return _error("size", "size_failed", str(e))
berkeley = _parse_size_berkeley(berkeley_output)
sections = _parse_size_sysv(sysv_output)
if not berkeley:
return _error("size", "parse_failed", "无法解析 size 输出")
summary = dict(berkeley)
summary["flash_used"] = berkeley["text"] + berkeley["data"]
summary["ram_used"] = berkeley["data"] + berkeley["bss"]
details = {
"elf_file": str(elf_path),
"sections": sections,
}
# 解析链接脚本计算使用率
if linker_script:
ld_path = Path(linker_script).resolve()
if ld_path.exists():
regions = _parse_linker_script(str(ld_path))
details["linker_script"] = str(ld_path)
if "FLASH" in regions:
flash_total = regions["FLASH"]["length"]
summary["flash_total"] = flash_total
summary["flash_percent"] = round(
summary["flash_used"] / flash_total * 100, 2
)
if "RAM" in regions:
ram_total = regions["RAM"]["length"]
summary["ram_total"] = ram_total
summary["ram_percent"] = round(
summary["ram_used"] / ram_total * 100, 2
)
return {
"status": "ok",
"action": "size",
"summary": summary,
"details": details,
}
def compare(elf1: str, elf2: str, toolchain_prefix: str,
toolchain_path: str) -> dict:
"""对比两个 ELF 的大小"""
size_exe = _find_size_tool(toolchain_prefix, toolchain_path)
results = {}
for label, path in [("baseline", elf1), ("current", elf2)]:
elf_path = Path(path).resolve()
if not elf_path.exists():
return _error("compare", "elf_not_found", f"ELF 文件不存在: {elf_path}")
try:
output = _run_size(size_exe, str(elf_path), "B")
except (RuntimeError, FileNotFoundError, subprocess.TimeoutExpired) as e:
return _error("compare", "size_failed", str(e))
parsed = _parse_size_berkeley(output)
if not parsed:
return _error("compare", "parse_failed", f"无法解析: {elf_path}")
parsed["elf"] = str(elf_path)
results[label] = parsed
b, c = results["baseline"], results["current"]
summary = {
"delta_text": c["text"] - b["text"],
"delta_data": c["data"] - b["data"],
"delta_bss": c["bss"] - b["bss"],
"delta_total": c["total"] - b["total"],
}
return {
"status": "ok",
"action": "compare",
"summary": summary,
"details": results,
}
def _error(action: str, code: str, message: str) -> dict:
return {
"status": "error",
"action": action,
"error": {"code": code, "message": message},
}
def output_json(data: dict):
sys.stdout.reconfigure(encoding="utf-8")
print(json.dumps(data, ensure_ascii=False, indent=2))
def main():
parser = argparse.ArgumentParser(description="GCC 嵌入式 ELF 大小分析")
sub = parser.add_subparsers(dest="command")
analyze_p = sub.add_parser("analyze", help="分析 ELF 大小")
analyze_p.add_argument("--elf", required=True, help="ELF 文件路径")
analyze_p.add_argument("--toolchain-prefix", default="arm-none-eabi-")
analyze_p.add_argument("--toolchain-path", default="")
analyze_p.add_argument("--linker-script", default="", help="链接脚本路径")
analyze_p.add_argument("--json", action="store_true", dest="as_json")
compare_p = sub.add_parser("compare", help="对比两个 ELF 大小")
compare_p.add_argument("--elf", required=True, help="基准 ELF")
compare_p.add_argument("--compare", required=True, help="对比 ELF")
compare_p.add_argument("--toolchain-prefix", default="arm-none-eabi-")
compare_p.add_argument("--toolchain-path", default="")
compare_p.add_argument("--json", action="store_true", dest="as_json")
args = parser.parse_args()
if args.command == "analyze":
result = analyze(
elf=args.elf,
toolchain_prefix=args.toolchain_prefix,
toolchain_path=args.toolchain_path,
linker_script=args.linker_script,
)
if args.as_json:
output_json(result)
else:
if result["status"] == "ok":
s = result["summary"]
print(f"ELF: {result['details']['elf_file']}")
print(f" text: {s['text']:>8} data: {s['data']:>8} bss: {s['bss']:>8} total: {s['total']:>8}")
print(f" Flash: {s['flash_used']:>8} bytes", end="")
if "flash_total" in s:
print(f" / {s['flash_total']} ({s['flash_percent']}%)", end="")
print()
print(f" RAM: {s['ram_used']:>8} bytes", end="")
if "ram_total" in s:
print(f" / {s['ram_total']} ({s['ram_percent']}%)", end="")
print()
else:
print(f"错误: {result['error']['message']}", file=sys.stderr)
sys.exit(1)
elif args.command == "compare":
result = compare(
elf1=args.elf, elf2=args.compare,
toolchain_prefix=args.toolchain_prefix,
toolchain_path=args.toolchain_path,
)
if args.as_json:
output_json(result)
else:
if result["status"] == "ok":
s = result["summary"]
d = result["details"]
print(f"基准: {d['baseline']['elf']}")
print(f"对比: {d['current']['elf']}")
for key in ("delta_text", "delta_data", "delta_bss", "delta_total"):
val = s[key]
sign = "+" if val > 0 else ""
print(f" {key.replace('delta_', ''):>5}: {sign}{val} bytes")
else:
print(f"错误: {result['error']['message']}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
How it compares
Use instead of ad-hoc shell cmake chains when you want preset-aware scan, build, and ELF sizing in one skill.
FAQ
Who is gcc for?
and small-team embedded developers using Claude Code, Cursor, or Codex on CMake + ARM GCC firmware repos.
When should I use gcc?
During Build when you need to enumerate presets, run configure/build/clean, or analyze ELF memory before handing binaries to a debugger skill.
Is gcc safe to install?
It runs local cmake and compiler commands on your machine; review the Security Audits panel on this page and use operation_mode 2 or 3 if you want summaries or confirmation before execution.