From d6941e1d2f2c97f289364884ce627dab1dd51c23 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Fri, 21 Nov 2025 18:03:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(cbuild):=20=E6=B7=BB=E5=8A=A0=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=B7=AF=E5=BE=84=E5=8E=BB=E9=87=8D=E4=B8=8E=E7=9B=B8?= =?UTF-8?q?=E5=AF=B9=E5=8C=96=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 `_path_collection` 方法用于统一处理路径的解析、去重及相对化, 优化对象文件路径生成逻辑,支持更安全的路径映射机制,防止文件冲突。 同时添加对构建目录的清理功能(clean 命令),完善构建生命周期管理。 主要变更包括: - 引入 `hashlib` 模块以支持路径哈希命名 - 重构 `get_build_components` 和 `get_object_path` 方法 - 新增 `clean` 命令及相关实现 - 改进命令行参数支持,增加 "clean" 选项 --- cbuild.py | 75 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/cbuild.py b/cbuild.py index 3502d15..6925ebc 100644 --- a/cbuild.py +++ b/cbuild.py @@ -1,4 +1,4 @@ -# build.py +"""cbuild.py""" from abc import ABC, abstractmethod import tomllib import pprint @@ -7,6 +7,7 @@ from pathlib import Path from dataclasses import dataclass, field from enum import Enum, auto import logging +import hashlib import argparse import sys @@ -186,9 +187,15 @@ class CBuildContext: """构建上下文,管理所有包的信息""" def __init__(self, package: PackageConfig, build_path: BuildPath | None = None): self.package = package + self.relative_path = Path.cwd() self.path = BuildPath.from_root_path(package.path) \ if build_path is None else build_path + def _path_collection(self, path: list[Path]) -> list[Path]: + p = [str(p.resolve()) for p in path if p.exists()] + unique_paths = set(p) + return [Path(p).relative_to(self.relative_path) for p in unique_paths] + @property def sources_path(self) -> list[Path]: """获取所有包的源文件路径""" @@ -212,8 +219,7 @@ class CBuildContext: deps = self.get_dependencies() for dep in deps: includes.extend(dep.includes) - return [inc for inc in includes if inc.exists()] - + return self._path_collection(includes) def get_targets(self) -> list[Target]: """获取所有构建目标""" @@ -254,21 +260,28 @@ class CBuildContext: return targets - def get_build_components(self) -> list[tuple[Path, Path]]: - """获取所有需要编译的源文件及目标文件(排除主文件即main.c, lib.c, bin/*.c)""" + def get_self_build_objs(self) -> list[Path]: + """获取当前包的源文件路径""" objs = [] for path in self.sources_path: if path == self.path.default_bin_path: continue if path == self.path.default_lib_path: continue - objs.append((path, self.get_object_path(path))) - # logging.debug("[+] build_components: %s", objs) - deps = self.get_dependencies() - for dep in deps: - objs.extend(dep.get_build_components()) + objs.append(path) return objs + def get_build_components(self) -> list[tuple[Path, Path]]: + """获取所有需要编译的源文件及目标文件(排除主文件即main.c, lib.c, bin/*.c)""" + # logging.debug("[+] build_components: %s", objs) + deps = [self] + deps.extend(self.get_dependencies()) + objs = [] + for dep in deps: + objs.extend(dep.get_self_build_objs()) + self._path_collection(objs) + return [(source_path, self.get_object_path(source_path)) for source_path in objs] + def get_object_path(self, source_path: Path) -> Path: """将源文件路径映射到对象文件路径 @@ -282,16 +295,22 @@ class CBuildContext: objects_dir = self.path.object_path objects_dir.mkdir(parents=True, exist_ok=True) - # 生成相对于src目录的路径结构 try: - relative_path = source_path.relative_to(self.path.src_path) - object_path = objects_dir / relative_path.with_suffix('.o') - # 确保对象文件的目录存在 - object_path.parent.mkdir(parents=True, exist_ok=True) - return object_path + # 尝试生成相对于根目录的路径结构,避免冲突 + relative_path = source_path.relative_to(self.path.root_path) + # 将路径分隔符替换为特殊字符,防止文件系统问题 + safe_relative_path = str(relative_path).replace('/', '_').replace('\\', '_') + object_path = objects_dir / Path(safe_relative_path).with_suffix('.o') except ValueError: - # 如果源文件不在src目录下,使用文件名作为对象文件名 - return objects_dir / source_path.with_suffix('.o').name + # 如果源文件完全不在项目目录下,则使用完整路径哈希 + full_path_str = str(source_path.absolute()) + path_hash = hashlib.md5(full_path_str.encode()).hexdigest()[:8] + object_filename = f"{source_path.stem}_{path_hash}{source_path.suffix}.o" + object_path = objects_dir / object_filename + + # 确保对象文件的目录存在 + object_path.parent.mkdir(parents=True, exist_ok=True) + return object_path def get_dependencies(self) -> list[Self]: """_summary_ @@ -333,7 +352,6 @@ class Compiler(ABC): for source, output in sources: self.compile(source, output, includes, flags) - def cmd(self, cmd: str | list[str]): """执行命令并处理错误输出""" try: @@ -377,10 +395,9 @@ class GccCompiler(Compiler): def link(self, objects: list[Path], libraries: list[str], flags: list[str], output: Path): """链接对象文件""" cmd = ["gcc"] - objs = set(str(i.absolute()) for i in objects) cmd.extend(flags) cmd.extend(["-o", str(output)]) - cmd.extend(objs) + cmd.extend(str(obj) for obj in objects) cmd.extend(lib for lib in libraries) self.cmd(cmd) @@ -434,6 +451,16 @@ class CPackageBuilder: logging.error("not have target to run") subprocess.run(targets[0].output, check=False) + def clean(self): + """清理构建产物""" + # 删除整个输出目录,这会清除所有构建产物 + if self.context.path.output_path.exists(): + import shutil + shutil.rmtree(self.context.path.output_path) + print(f"已清理构建目录: {self.context.path.output_path}") + else: + print("没有找到构建目录,无需清理") + def tests(self): """运行测试""" targets = [target for target in self.context.get_targets() \ @@ -471,7 +498,7 @@ class CPackageBuilder: def main(): """main""" parser = argparse.ArgumentParser(description="Simple C Package Manager") - parser.add_argument("command", choices=["build", "run", "test"], + parser.add_argument("command", choices=["build", "run", "test", "clean"], help="Command to execute") parser.add_argument("--verbose", "-v", action="store_true", help="enable the logging to debug (defalut: false)") @@ -502,6 +529,10 @@ def main(): builder = CPackageBuilder(package_path, compiler) builder.build() builder.tests() + elif args.command == "clean": + builder = CPackageBuilder(package_path, compiler) + builder.clean() + print("clean is Ok...") except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1)