feat(cbuild): 添加构建路径去重与相对化处理
新增 `_path_collection` 方法用于统一处理路径的解析、去重及相对化, 优化对象文件路径生成逻辑,支持更安全的路径映射机制,防止文件冲突。 同时添加对构建目录的清理功能(clean 命令),完善构建生命周期管理。 主要变更包括: - 引入 `hashlib` 模块以支持路径哈希命名 - 重构 `get_build_components` 和 `get_object_path` 方法 - 新增 `clean` 命令及相关实现 - 改进命令行参数支持,增加 "clean" 选项
This commit is contained in:
75
cbuild.py
75
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)
|
||||
|
||||
Reference in New Issue
Block a user