为什么我喜欢开发 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()