为什么我喜欢开发 Rust 而不是 C?就是因为 C 的开发体验太差。可是回避问题是不会带来受益的,所以我决定向 C 开发工作流发起进攻!

  • Meson
  • Ninja
  • Clang
    • clang-format
    • clang-tidy
    • clangd
  • VS Code
  • gcc

类 Cargo 脚本

#!/usr/bin/env python3
"""
C Project Builder - Like cargo for C projects

Usage:
  ./build.py build        # 构建 debug 版本 (默认)
  ./build.py build --release  # 构建 release 版本
  ./build.py run          # 运行 debug 版本
  ./build.py run --release    # 运行 release 版本
  ./build.py clean        # 清理所有构建
"""

import argparse
import subprocess
import sys
import os
import json
import shutil

def run_command(cmd, cwd=None):
    """执行命令并处理错误"""
    try:
        print(f" executing: {' '.join(cmd)}")
        subprocess.run(cmd, check=True, cwd=cwd)
    except subprocess.CalledProcessError as e:
        print(f"\033[91mError: Command failed with exit code {e.returncode}\033[0m")
        sys.exit(1)
    except FileNotFoundError:
        print(
            f"\033[91mError: '{cmd[0]}' not found. Make sure Meson and Ninja are installed.\033[0m"
        )
        print("Install with: pip install meson && sudo apt install ninja-build")
        sys.exit(1)

# 程序可能不够健壮,因为项目可能多个编译文件,且不一定是 executable 类型
def get_project_info():
    """使用 meson introspect 可靠获取项目信息"""
    try:
        # 检查 build 目录是否存在(需要先配置)
        if not os.path.exists("build/debug"):
            setup_build_dir("debug")  # 确保有构建目录

        # 使用 meson introspect 获取目标名
        targets = subprocess.check_output(
            ["meson", "introspect", "build/debug", "--targets"], text=True
        )
        for target in json.loads(targets):
            if target["type"] == "executable":
                return {"name": target["name"], "files": target["filename"]}
    except Exception as e:
        print(f"\033[93m⚠️  Failed to get target name: {str(e)}\033[0m")

    # 不提供回退方案,改为抛出错误
    raise Exception("Failed to get target name")

def setup_build_dir(build_type):
    """配置构建目录,使用专业构建参数"""
    build_dir = f"build/{build_type}"

    # 如果构建目录不存在,进行初始化
    if not os.path.exists(build_dir):
        os.makedirs(build_dir, exist_ok=True)
        print(f"\n\033[94m⚙️  Configuring {build_type} build...\033[0m")

        # 构建 Meson 命令 - 使用您指定的专业参数
        if build_type == "debug":
            meson_cmd = [
                "meson",
                "setup",
                build_dir,
                "--buildtype=debug",
                "-Db_ndebug=false",
            ]
            opt_info = "without optimizations (debug mode)"
        else:  # release
            meson_cmd = [
                "meson",
                "setup",
                build_dir,
                "--buildtype=release",
                "-Db_ndebug=true",
                "-Db_lto=true",
                "-Db_sanitize=none",
                "-Doptimization=2",
                "-Db_staticpic=false",
            ]
            opt_info = "with LTO and optimizations (release mode)"

        run_command(meson_cmd)
        print(f"\033[92m✓ {build_type.capitalize()} build configured {opt_info}\033[0m")

    return build_dir

def build_project(build_type="debug"):
    """构建项目"""
    build_dir = setup_build_dir(build_type)

    print(f"\n\033[94m🔨 Building {build_type} version...\033[0m")
    run_command(["ninja", "-C", build_dir])

    # 获取可执行文件路径
    project_info = get_project_info()
    exe_path = f"{build_dir}/{project_info['name']}"

    for file in project_info["files"]:
        if os.path.exists(file):
            size = os.path.getsize(file)
            if size < 1024:
                size_str = f"{size} bytes"
            elif size < 1024 * 1024:
                size_str = f"{size/1024:.1f} KB"
            else:
                size_str = f"{size/(1024*1024):.2f} MB"

            print(f"\033[92m✓ Build successful! Executable: {file} ({size_str})\033[0m")
    return exe_path

def run_project(build_type="debug"):
    """运行项目"""
    # 确保项目已构建
    exe_path = build_project(build_type)

    print(f"\n\033[94m🚀 Running {build_type} version...\033[0m")
    print("\033[93m" + "=" * 50 + "\033[0m")

    try:
        print(f" executing: {exe_path}")
        subprocess.run([exe_path], check=True)
        print("\033[93m" + "=" * 50 + "\033[0m")
        print(f"\033[92m✓ Program exited successfully\033[0m")
    except subprocess.CalledProcessError as e:
        print(f"\033[91m✘ Program exited with code {e.returncode}\033[0m")

def clean_build():
    """跨平台清理构建目录"""
    build_dir = "build"
    if os.path.exists(build_dir):
        print("\033[94m🧹 Cleaning build directories...\033[0m")
        try:
            shutil.rmtree(build_dir)
            print("\033[92m✓ Build directories cleaned\033[0m")
        except Exception as e:
            print(f"\033[91mError cleaning: {str(e)}\033[0m")
            sys.exit(1)
    else:
        print("\033[93mNo build directories to clean\033[0m")

def main():
    parser = argparse.ArgumentParser(description="C Project Builder (like cargo)")
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # Build 命令
    build_parser = subparsers.add_parser("build", help="Build the project")
    build_parser.add_argument(
        "--release", action="store_true", help="Build release version"
    )
    build_parser.add_argument(
        "--debug", action="store_true", help="Build debug version (default)"
    )

    # Run 命令
    run_parser = subparsers.add_parser("run", help="Build and run the project")
    run_parser.add_argument(
        "--release", action="store_true", help="Run release version"
    )
    run_parser.add_argument(
        "--debug", action="store_true", help="Run debug version (default)"
    )

    # Clean 命令
    subparsers.add_parser("clean", help="Clean all build artifacts")

    # Version 命令
    subparsers.add_parser("version", help="Show version information")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        print("\nExample usage:")
        print("  ./build.py build        # Build debug version")
        print("  ./build.py build --release  # Build release version")
        print("  ./build.py run          # Build and run debug version")
        sys.exit(1)

    # ===== FIXED SECTION START =====
    # Only determine build type for build/run commands
    build_type = "debug"  # Default value
    if args.command in ["build", "run"]:
        # Check for conflicting flags
        if args.release and args.debug:
            print("\033[91mError: Cannot specify both --release and --debug\033[0m")
            sys.exit(1)
        build_type = "release" if args.release else "debug"
    # ===== FIXED SECTION END =====

    # 执行命令
    if args.command == "build":
        build_project(build_type)
    elif args.command == "run":
        run_project(build_type)
    elif args.command == "clean":
        clean_build()
    elif args.command == "version":
        project_info = get_project_info()
        print(f"{project_info['name']} builder v1.1 (professional C build system)")

if __name__ == "__main__":
    # 确保脚本有执行权限
    if not os.access(__file__, os.X_OK):
        print("\033[93m⚠️  Please make this script executable: chmod +x build.py\033[0m")

    main()