feat(cbuild): 重构构建系统并迁移至 tools/cbuild
将 `cbuild.py` 迁移至 `tools/cbuild/` 并进行大量功能增强。引入依赖解析器、支持颜色日志输出、 改进包配置默认值处理、完善构建目标识别与拓扑排序依赖管理。同时添加 `.gitignore` 和 `pyproject.toml` 以支持标准 Python 包结构,并更新 README 文档。 新增命令支持:tree(显示依赖树)、clean(带文件统计)、test(运行测试)等, 优化了 Windows 平台下的可执行文件扩展名处理逻辑。 移除了旧的 `wc.py` 行数统计脚本。
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "smcc_lex"
|
name = "smcc_lex"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [{ name = "libcore", path = "../../runtime/libcore" }]
|
||||||
{ name = "libcore", path = "../../runtime/libcore" },
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libcore"
|
name = "libcore"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
default_features = [
|
default_features = ["std_impl"]
|
||||||
"std_impl",
|
features = ["std_impl"]
|
||||||
]
|
|
||||||
features = [
|
|
||||||
"std_impl",
|
|
||||||
]
|
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
# TODO define some to disable stdio for self-contained build
|
# TODO define some to disable stdio for self-contained build
|
||||||
{ name = "log", path = "../log" }
|
{ name = "log", path = "../log" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "core_mem.h"
|
#include "core_mem.h"
|
||||||
#include "core_str.h"
|
#include "core_str.h"
|
||||||
|
|
||||||
|
struct core_stream;
|
||||||
typedef struct core_stream core_stream_t;
|
typedef struct core_stream core_stream_t;
|
||||||
|
|
||||||
#define core_stream_eof (-1)
|
#define core_stream_eof (-1)
|
||||||
|
|||||||
10
tools/cbuild/.gitignore
vendored
Normal file
10
tools/cbuild/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
9
tools/cbuild/README.md
Normal file
9
tools/cbuild/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
# cbuild
|
||||||
|
|
||||||
|
## dependencies
|
||||||
|
[uv](https://docs.astral.sh/uv/)
|
||||||
|
一个用 Rust 编写的极速 Python 包和项目管理工具。
|
||||||
|
|
||||||
|
## install
|
||||||
|
使用 `uv pip install -e .` 将 `cbuild` 添加到 python 环境
|
||||||
@@ -8,10 +8,49 @@ from dataclasses import dataclass, field
|
|||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from graphlib import TopologicalSorter
|
||||||
|
import shutil
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class ColorFormatter(logging.Formatter):
|
||||||
|
"""颜色格式化"""
|
||||||
|
# 基础颜色定义(对应ANSI颜色码)
|
||||||
|
cyan = "\x1b[36m" # DEBUG - 青色
|
||||||
|
green = "\x1b[32m" # INFO - 绿色
|
||||||
|
blue = "\x1b[34m" # TRACE - 蓝色
|
||||||
|
yellow = "\x1b[33m" # WARN - 黄色
|
||||||
|
red = "\x1b[31m" # ERROR - 红色
|
||||||
|
line_red = "\x1b[31;4m" # FATAL - 红色加下划线
|
||||||
|
bold = "\x1b[1m" # 粗体
|
||||||
|
reset = "\x1b[0m" # 重置
|
||||||
|
|
||||||
|
fmt = "%(name)s - %(levelname)s: "
|
||||||
|
time = "%(asctime)s"
|
||||||
|
msg = "%(message)s"
|
||||||
|
file = " - (%(filename)s:%(lineno)d)"
|
||||||
|
|
||||||
|
FORMATS = {
|
||||||
|
logging.DEBUG: bold + cyan + fmt + reset + msg + file,
|
||||||
|
logging.INFO: bold + green + fmt + reset + msg,
|
||||||
|
logging.WARNING: bold + yellow + fmt + reset + msg,
|
||||||
|
logging.ERROR: bold + red + fmt + reset + msg,
|
||||||
|
logging.CRITICAL: bold + line_red + fmt + reset + msg + file,
|
||||||
|
logging.FATAL: bold + line_red + fmt + reset + msg + file,
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
log_fmt = self.FORMATS.get(record.levelno)
|
||||||
|
formatter = logging.Formatter(log_fmt)
|
||||||
|
return formatter.format(record)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
color_formatter = ColorFormatter()
|
||||||
|
console_handler.setFormatter(color_formatter)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Dependency:
|
class Dependency:
|
||||||
@@ -51,7 +90,7 @@ class PackageConfig:
|
|||||||
Returns:
|
Returns:
|
||||||
str: 包的名称,如果未定义则返回空字符串
|
str: 包的名称,如果未定义则返回空字符串
|
||||||
"""
|
"""
|
||||||
return self.config.get("name", "")
|
return self.config.get("name", "[unnamed package]")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self) -> str:
|
def version(self) -> str:
|
||||||
@@ -60,7 +99,7 @@ class PackageConfig:
|
|||||||
Returns:
|
Returns:
|
||||||
str: 包的版本号,如果未定义则返回空字符串
|
str: 包的版本号,如果未定义则返回空字符串
|
||||||
"""
|
"""
|
||||||
return self.config.get("version", "")
|
return self.config.get("version", "0.0.0")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_features(self) -> list[str]:
|
def default_features(self) -> list[str]:
|
||||||
@@ -133,6 +172,68 @@ class PackageConfig:
|
|||||||
"""
|
"""
|
||||||
return self.config.get("description", "")
|
return self.config.get("description", "")
|
||||||
|
|
||||||
|
class DependencyResolver:
|
||||||
|
"""依赖解析器"""
|
||||||
|
def __init__(self, root_package: PackageConfig):
|
||||||
|
self.root_package = root_package
|
||||||
|
self.resolved_deps: dict[str, PackageConfig] = {}
|
||||||
|
self.deps_graph: TopologicalSorter = TopologicalSorter()
|
||||||
|
self._resolved = False
|
||||||
|
|
||||||
|
def resolve(self) -> dict[str, PackageConfig]:
|
||||||
|
"""解析所有依赖"""
|
||||||
|
if self._resolved:
|
||||||
|
return self.resolved_deps
|
||||||
|
|
||||||
|
# 使用广度优先搜索解析所有依赖
|
||||||
|
queue = [self.root_package]
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
pkg_config = queue.pop(0)
|
||||||
|
pkg_name = pkg_config.name
|
||||||
|
|
||||||
|
if pkg_name in visited:
|
||||||
|
continue
|
||||||
|
visited.add(pkg_name)
|
||||||
|
|
||||||
|
# 创建构建上下文
|
||||||
|
if pkg_name not in self.resolved_deps:
|
||||||
|
self.resolved_deps[pkg_name] = pkg_config
|
||||||
|
|
||||||
|
# 解析直接依赖
|
||||||
|
for dep in pkg_config.dependencies:
|
||||||
|
dep_path = Path(pkg_config.path / dep.path)
|
||||||
|
dep_config = PackageConfig(dep_path)
|
||||||
|
dep_name = dep_config.name
|
||||||
|
|
||||||
|
# 添加依赖图关系
|
||||||
|
self.deps_graph.add(pkg_name, dep_name)
|
||||||
|
|
||||||
|
# 如果是新依赖,加入队列继续解析
|
||||||
|
if dep_name not in visited and dep_name not in self.resolved_deps:
|
||||||
|
queue.append(dep_config)
|
||||||
|
|
||||||
|
self.deps_graph.prepare()
|
||||||
|
self._resolved = True
|
||||||
|
return self.resolved_deps
|
||||||
|
|
||||||
|
def get_sorted_dependencies(self) -> list[CBuildContext]:
|
||||||
|
"""获取按拓扑排序的依赖列表(不包括根包)"""
|
||||||
|
if not self._resolved:
|
||||||
|
self.resolve()
|
||||||
|
|
||||||
|
sorted_names = list(self.deps_graph.static_order())
|
||||||
|
return [CBuildContext(self.resolved_deps[name]) for name in sorted_names
|
||||||
|
if name != self.root_package.name and name in self.resolved_deps]
|
||||||
|
|
||||||
|
def get_all_contexts(self) -> list[CBuildContext]:
|
||||||
|
"""获取所有上下文(包括根包)"""
|
||||||
|
if not self._resolved:
|
||||||
|
self.resolve()
|
||||||
|
|
||||||
|
return [CBuildContext(resolved_dep) for resolved_dep in self.resolved_deps.values()]
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class BuildPath:
|
class BuildPath:
|
||||||
"""path"""
|
"""path"""
|
||||||
@@ -141,6 +242,7 @@ class BuildPath:
|
|||||||
output_path: Path
|
output_path: Path
|
||||||
object_path: Path
|
object_path: Path
|
||||||
src_path: Path
|
src_path: Path
|
||||||
|
inc_path: Path
|
||||||
default_bin_path: Path
|
default_bin_path: Path
|
||||||
default_lib_path: Path
|
default_lib_path: Path
|
||||||
|
|
||||||
@@ -159,6 +261,7 @@ class BuildPath:
|
|||||||
return cls(
|
return cls(
|
||||||
root_path = root_path,
|
root_path = root_path,
|
||||||
tests_path = root_path / "tests",
|
tests_path = root_path / "tests",
|
||||||
|
inc_path = root_path / "include",
|
||||||
output_path = output_path ,
|
output_path = output_path ,
|
||||||
object_path = output_path / "obj",
|
object_path = output_path / "obj",
|
||||||
src_path = src_path,
|
src_path = src_path,
|
||||||
@@ -190,11 +293,12 @@ class CBuildContext:
|
|||||||
self.relative_path = Path.cwd()
|
self.relative_path = Path.cwd()
|
||||||
self.path = BuildPath.from_root_path(package.path) \
|
self.path = BuildPath.from_root_path(package.path) \
|
||||||
if build_path is None else build_path
|
if build_path is None else build_path
|
||||||
|
self.deps_resolver = DependencyResolver(package)
|
||||||
|
|
||||||
def _path_collection(self, path: list[Path]) -> list[Path]:
|
def _path_collection(self, path: list[Path]) -> list[Path]:
|
||||||
p = [str(p.resolve()) for p in path if p.exists()]
|
p = [str(p.resolve()) for p in path if p.exists()]
|
||||||
unique_paths = set(p)
|
unique_paths = set(p)
|
||||||
return [Path(p).relative_to(self.relative_path) for p in unique_paths]
|
return [Path(p).relative_to(self.relative_path, walk_up=True) for p in unique_paths]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sources_path(self) -> list[Path]:
|
def sources_path(self) -> list[Path]:
|
||||||
@@ -214,18 +318,16 @@ class CBuildContext:
|
|||||||
@property
|
@property
|
||||||
def includes(self) -> list[Path]:
|
def includes(self) -> list[Path]:
|
||||||
"""获取包的包含路径"""
|
"""获取包的包含路径"""
|
||||||
includes = [self.path.root_path / "include"]
|
deps = self.deps_resolver.get_all_contexts()
|
||||||
# check folders available
|
includes = [inc.path.inc_path for inc in deps if inc.path.inc_path.exists()]
|
||||||
deps = self.get_dependencies()
|
|
||||||
for dep in deps:
|
|
||||||
includes.extend(dep.includes)
|
|
||||||
return self._path_collection(includes)
|
return self._path_collection(includes)
|
||||||
|
|
||||||
def get_targets(self) -> list[Target]:
|
def get_targets(self) -> list[Target]:
|
||||||
"""获取所有构建目标"""
|
"""获取所有构建目标"""
|
||||||
targets = []
|
targets = []
|
||||||
# TODO
|
execute_ext = ""
|
||||||
ext = ".exe"
|
if sys.platform == "win32":
|
||||||
|
execute_ext = ".exe"
|
||||||
|
|
||||||
# 添加主可执行文件目标
|
# 添加主可执行文件目标
|
||||||
if self.path.default_bin_path.exists():
|
if self.path.default_bin_path.exists():
|
||||||
@@ -234,7 +336,7 @@ class CBuildContext:
|
|||||||
type=TargetType.MAIN_EXECUTABLE,
|
type=TargetType.MAIN_EXECUTABLE,
|
||||||
source=self.path.default_bin_path,
|
source=self.path.default_bin_path,
|
||||||
object=self.get_object_path(self.path.default_bin_path),
|
object=self.get_object_path(self.path.default_bin_path),
|
||||||
output=self.path.output_path / f"{self.package.name}{ext}",
|
output=self.path.output_path / f"{self.package.name}{execute_ext}",
|
||||||
))
|
))
|
||||||
|
|
||||||
# 添加静态库目标
|
# 添加静态库目标
|
||||||
@@ -255,7 +357,7 @@ class CBuildContext:
|
|||||||
type=TargetType.TEST_EXECUTABLE,
|
type=TargetType.TEST_EXECUTABLE,
|
||||||
source=test_source,
|
source=test_source,
|
||||||
object=self.get_object_path(test_source),
|
object=self.get_object_path(test_source),
|
||||||
output=self.path.output_path / f"{test_source.stem}{ext}"
|
output=self.path.output_path / f"{test_source.stem}{execute_ext}"
|
||||||
))
|
))
|
||||||
|
|
||||||
return targets
|
return targets
|
||||||
@@ -273,9 +375,7 @@ class CBuildContext:
|
|||||||
|
|
||||||
def get_build_components(self) -> list[tuple[Path, Path]]:
|
def get_build_components(self) -> list[tuple[Path, Path]]:
|
||||||
"""获取所有需要编译的源文件及目标文件(排除主文件即main.c, lib.c, bin/*.c)"""
|
"""获取所有需要编译的源文件及目标文件(排除主文件即main.c, lib.c, bin/*.c)"""
|
||||||
# logging.debug("[+] build_components: %s", objs)
|
deps = self.deps_resolver.get_all_contexts()
|
||||||
deps = [self]
|
|
||||||
deps.extend(self.get_dependencies())
|
|
||||||
objs = []
|
objs = []
|
||||||
for dep in deps:
|
for dep in deps:
|
||||||
objs.extend(dep.get_self_build_objs())
|
objs.extend(dep.get_self_build_objs())
|
||||||
@@ -312,19 +412,6 @@ class CBuildContext:
|
|||||||
object_path.parent.mkdir(parents=True, exist_ok=True)
|
object_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
return object_path
|
return object_path
|
||||||
|
|
||||||
def get_dependencies(self) -> list[Self]:
|
|
||||||
"""_summary_
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[CBuildContext]: _description_
|
|
||||||
"""
|
|
||||||
deps = []
|
|
||||||
for dep in self.package.dependencies:
|
|
||||||
ctx = CBuildContext(PackageConfig(Path(self.package.path / dep.path)))
|
|
||||||
deps.append(ctx)
|
|
||||||
deps.extend(ctx.get_dependencies())
|
|
||||||
return deps
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CacheEntry:
|
class CacheEntry:
|
||||||
"""缓存条目"""
|
"""缓存条目"""
|
||||||
@@ -355,12 +442,12 @@ class Compiler(ABC):
|
|||||||
def cmd(self, cmd: str | list[str]):
|
def cmd(self, cmd: str | list[str]):
|
||||||
"""执行命令并处理错误输出"""
|
"""执行命令并处理错误输出"""
|
||||||
try:
|
try:
|
||||||
logging.debug("command: `%s`", ' '.join(cmd) if isinstance(cmd, list) else cmd)
|
logger.debug("command: `%s`", ' '.join(cmd) if isinstance(cmd, list) else cmd)
|
||||||
subprocess.run(cmd, check=True)
|
subprocess.run(cmd, check=True)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# 输出详细错误信息帮助调试
|
# 输出详细错误信息帮助调试
|
||||||
cmd_str = ' '.join(e.cmd) if isinstance(e.cmd, list) else e.cmd
|
cmd_str = ' '.join(e.cmd) if isinstance(e.cmd, list) else e.cmd
|
||||||
logging.error("command running error: [%d]`%s`", e.returncode, cmd_str)
|
logger.error("command running error: [%d]`%s`", e.returncode, cmd_str)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
class ClangCompiler(Compiler):
|
class ClangCompiler(Compiler):
|
||||||
@@ -403,9 +490,6 @@ class GccCompiler(Compiler):
|
|||||||
|
|
||||||
class CPackageBuilder:
|
class CPackageBuilder:
|
||||||
"""包构建器"""
|
"""包构建器"""
|
||||||
# TODO
|
|
||||||
EXT = ".exe"
|
|
||||||
|
|
||||||
def __init__(self, package_path: Path, compiler: Compiler):
|
def __init__(self, package_path: Path, compiler: Compiler):
|
||||||
self.package = PackageConfig(package_path)
|
self.package = PackageConfig(package_path)
|
||||||
self.context = CBuildContext(self.package, None)
|
self.context = CBuildContext(self.package, None)
|
||||||
@@ -417,17 +501,35 @@ class CPackageBuilder:
|
|||||||
# 确保输出目录存在
|
# 确保输出目录存在
|
||||||
self.context.path.output_path.mkdir(parents=True, exist_ok=True)
|
self.context.path.output_path.mkdir(parents=True, exist_ok=True)
|
||||||
self.context.path.object_path.mkdir(parents=True, exist_ok=True)
|
self.context.path.object_path.mkdir(parents=True, exist_ok=True)
|
||||||
# TODO use cache and add dependency include and flags
|
|
||||||
deps = self.context.get_dependencies()
|
|
||||||
path_map = self.context.get_build_components()
|
path_map = self.context.get_build_components()
|
||||||
self.compiler.compile_all(path_map, self.context.includes, self.global_flags)
|
self.compiler.compile_all(path_map, self.context.includes, self.global_flags)
|
||||||
return [pair[1] for pair in path_map]
|
return [pair[1] for pair in path_map]
|
||||||
|
|
||||||
def build(self):
|
def _format_size(self, size_bytes):
|
||||||
|
"""格式化文件大小显示"""
|
||||||
|
if size_bytes == 0:
|
||||||
|
return "0B"
|
||||||
|
|
||||||
|
size_names = ["B", "KB", "MB", "GB"]
|
||||||
|
i = 0
|
||||||
|
while size_bytes >= 1024 and i < len(size_names) - 1:
|
||||||
|
size_bytes /= 1024.0
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
return f"{int(size_bytes)}{size_names[i]}"
|
||||||
|
return f"{size_bytes:.1f}{size_names[i]}"
|
||||||
|
|
||||||
|
def build(self, targets_type: list[TargetType]):
|
||||||
"""构建包"""
|
"""构建包"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
object_files = self._build_ctx()
|
object_files = self._build_ctx()
|
||||||
targets = self.context.get_targets()
|
targets = self.context.get_targets()
|
||||||
for target in targets:
|
for target in targets:
|
||||||
|
if target.type not in targets_type:
|
||||||
|
continue
|
||||||
|
|
||||||
match target.type:
|
match target.type:
|
||||||
case TargetType.MAIN_EXECUTABLE:
|
case TargetType.MAIN_EXECUTABLE:
|
||||||
self.compiler.compile(target.source, target.object, self.context.includes,
|
self.compiler.compile(target.source, target.object, self.context.includes,
|
||||||
@@ -441,25 +543,61 @@ class CPackageBuilder:
|
|||||||
object_files.append(target.object)
|
object_files.append(target.object)
|
||||||
self.compiler.link(object_files, [], self.global_flags, target.output)
|
self.compiler.link(object_files, [], self.global_flags, target.output)
|
||||||
object_files.remove(target.object)
|
object_files.remove(target.object)
|
||||||
logging.info("Building is Ok...")
|
# 计算构建时间
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
time_str = f"{elapsed_time:.2f}s" if elapsed_time < 60 else f"{elapsed_time/60:.1f}m"
|
||||||
|
# 显示完成信息
|
||||||
|
logger.info("\tFinished dev [unoptimized + debuginfo] target(s) in %s",
|
||||||
|
time_str)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""运行项目"""
|
"""运行项目"""
|
||||||
targets = [target for target in self.context.get_targets() \
|
targets = [target for target in self.context.get_targets() \
|
||||||
if target.type == TargetType.MAIN_EXECUTABLE]
|
if target.type == TargetType.MAIN_EXECUTABLE]
|
||||||
if len(targets) != 1:
|
if len(targets) != 1:
|
||||||
logging.error("not have target to run")
|
logger.error("not have target to run")
|
||||||
subprocess.run(targets[0].output, check=False)
|
subprocess.run(targets[0].output, check=False)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""清理构建产物"""
|
"""清理构建产物"""
|
||||||
|
# 统计要删除的文件数量和总大小
|
||||||
|
cleaned_files = 0
|
||||||
|
total_size = 0
|
||||||
# 删除整个输出目录,这会清除所有构建产物
|
# 删除整个输出目录,这会清除所有构建产物
|
||||||
if self.context.path.output_path.exists():
|
if self.context.path.output_path.exists():
|
||||||
import shutil
|
# 遍历目录统计文件数量和大小
|
||||||
|
for file_path in self.context.path.output_path.rglob("*"):
|
||||||
|
if file_path.is_file():
|
||||||
|
cleaned_files += 1
|
||||||
|
total_size += file_path.stat().st_size
|
||||||
shutil.rmtree(self.context.path.output_path)
|
shutil.rmtree(self.context.path.output_path)
|
||||||
print(f"已清理构建目录: {self.context.path.output_path}")
|
logger.info("已清理构建目录: %s",
|
||||||
|
self.context.path.output_path)
|
||||||
|
logger.info("Cleaned %s files, Size %s",
|
||||||
|
cleaned_files, self._format_size(total_size))
|
||||||
else:
|
else:
|
||||||
print("没有找到构建目录,无需清理")
|
logger.info("没有找到构建目录,无需清理")
|
||||||
|
|
||||||
|
def tree(self):
|
||||||
|
"""打印构建树 - 仿照 cargo 的风格"""
|
||||||
|
# TODO 递归显示
|
||||||
|
print("dependency tree:")
|
||||||
|
|
||||||
|
# 解析依赖
|
||||||
|
resolver = self.context.deps_resolver
|
||||||
|
resolver.resolve()
|
||||||
|
|
||||||
|
# 获取根包
|
||||||
|
root_pkg = resolver.root_package
|
||||||
|
print(f"{root_pkg.name} v{root_pkg.version}")
|
||||||
|
|
||||||
|
# 获取依赖图的节点和边
|
||||||
|
# 这里我们简化处理,只显示直接依赖
|
||||||
|
for pkg_config in resolver.resolved_deps.values():
|
||||||
|
if pkg_config.name != root_pkg.name: # 跳过根包
|
||||||
|
indent = "├── " if pkg_config.name != list(resolver.resolved_deps.keys())[-1]\
|
||||||
|
else "└── "
|
||||||
|
print(f"{indent}{pkg_config.name} v{pkg_config.version}")
|
||||||
|
|
||||||
def tests(self):
|
def tests(self):
|
||||||
"""运行测试"""
|
"""运行测试"""
|
||||||
@@ -469,8 +607,8 @@ class CPackageBuilder:
|
|||||||
failed = 0
|
failed = 0
|
||||||
for target in targets:
|
for target in targets:
|
||||||
name = target.name
|
name = target.name
|
||||||
print(f"运行测试: {name}")
|
logger.info("运行测试: %s", name)
|
||||||
logging.debug("test run %s", target.output)
|
logger.debug("test run %s", target.output)
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(target.output,
|
result = subprocess.run(target.output,
|
||||||
check=True,
|
check=True,
|
||||||
@@ -495,47 +633,67 @@ class CPackageBuilder:
|
|||||||
failed += 1
|
failed += 1
|
||||||
print(f"\n测试结果: {passed} 通过, {failed} 失败")
|
print(f"\n测试结果: {passed} 通过, {failed} 失败")
|
||||||
|
|
||||||
|
def cmd(self, cmd: str):
|
||||||
|
"""执行命令"""
|
||||||
|
match cmd:
|
||||||
|
case "build":
|
||||||
|
self.build([
|
||||||
|
TargetType.MAIN_EXECUTABLE,
|
||||||
|
TargetType.TEST_EXECUTABLE,
|
||||||
|
TargetType.STATIC_LIBRARY,
|
||||||
|
TargetType.SHARED_LIBRARY,
|
||||||
|
TargetType.EXECUTABLE,
|
||||||
|
])
|
||||||
|
case "run":
|
||||||
|
bin_path = self.context.path.default_bin_path
|
||||||
|
if not bin_path.exists():
|
||||||
|
logger.error("%s not exist", bin_path)
|
||||||
|
return
|
||||||
|
self.build([TargetType.MAIN_EXECUTABLE])
|
||||||
|
self.run()
|
||||||
|
case "test":
|
||||||
|
self.build([TargetType.TEST_EXECUTABLE])
|
||||||
|
self.tests()
|
||||||
|
case "clean":
|
||||||
|
self.clean()
|
||||||
|
case "tree":
|
||||||
|
self.tree()
|
||||||
|
case _:
|
||||||
|
logger.error("unknown command: %s", cmd)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""main"""
|
"""main"""
|
||||||
parser = argparse.ArgumentParser(description="Simple C Package Manager")
|
parser = argparse.ArgumentParser(description="Simple C Package Manager",
|
||||||
parser.add_argument("command", choices=["build", "run", "test", "clean"],
|
prog="cbuild")
|
||||||
|
parser.add_argument("command", choices=["build", "run", "test", "clean", "tree"],
|
||||||
help="Command to execute")
|
help="Command to execute")
|
||||||
parser.add_argument("--verbose", "-v", action="store_true",
|
parser.add_argument("--compiler", "-c", choices=["gcc", "clang", "smcc"], default="gcc",
|
||||||
help="enable the logging to debug (defalut: false)")
|
help="Compiler to use (default: gcc)")
|
||||||
|
parser.add_argument("--verbose", "-V", action="store_true",
|
||||||
|
help="enable the logging to debug (default: false)")
|
||||||
parser.add_argument("--path", "-p", default=".",
|
parser.add_argument("--path", "-p", default=".",
|
||||||
help="Path to the package (default: current directory)")
|
help="Path to the package (default: current directory)")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
package_path = Path(args.path)
|
package_path = Path.cwd() / Path(args.path)
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
logging.getLogger().setLevel(logging.NOTSET)
|
logger.setLevel(logging.DEBUG)
|
||||||
compiler = GccCompiler()
|
else:
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
try:
|
match args.compiler:
|
||||||
if args.command == "build":
|
case "gcc":
|
||||||
|
compiler = GccCompiler()
|
||||||
|
case "clang":
|
||||||
|
compiler = ClangCompiler()
|
||||||
|
case "smcc":
|
||||||
|
# TODO self compiler
|
||||||
|
raise ValueError("Invalid compiler")
|
||||||
|
case _:
|
||||||
|
raise ValueError("Invalid compiler")
|
||||||
builder = CPackageBuilder(package_path, compiler)
|
builder = CPackageBuilder(package_path, compiler)
|
||||||
builder.build()
|
builder.cmd(args.command)
|
||||||
print("build is Ok...")
|
|
||||||
elif args.command == "run":
|
|
||||||
builder = CPackageBuilder(package_path, compiler)
|
|
||||||
bin_path = builder.context.path.default_bin_path
|
|
||||||
if not bin_path.exists():
|
|
||||||
print(f"{bin_path} not exist")
|
|
||||||
return
|
|
||||||
builder.build()
|
|
||||||
builder.run()
|
|
||||||
elif args.command == "test":
|
|
||||||
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)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# builder = CPackageBuilder(Path("./runtime/libcore/"), ClangCompiler())
|
# builder = CPackageBuilder(Path("./runtime/libcore/"), ClangCompiler())
|
||||||
10
tools/cbuild/pyproject.toml
Normal file
10
tools/cbuild/pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[project]
|
||||||
|
name = "cbuild"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.14"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
cbuild = "cbuild:main"
|
||||||
48
wc.py
48
wc.py
@@ -1,48 +0,0 @@
|
|||||||
"""统计目录下C/C++文件的行数(write by AI)"""
|
|
||||||
import os
|
|
||||||
|
|
||||||
def count_lines(file_path):
|
|
||||||
"""统计单个文件的代码行数"""
|
|
||||||
try:
|
|
||||||
with open(file_path, 'rb') as f: # 二进制模式读取避免编码问题
|
|
||||||
return sum(1 for _ in f)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
print(f"警告:无法解码文件 {file_path}(可能不是文本文件)")
|
|
||||||
return 0
|
|
||||||
except Exception as e:
|
|
||||||
print(f"读取 {file_path} 出错: {str(e)}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def scan_files(directory, exclude_dirs=None):
|
|
||||||
"""扫描目录获取所有C/C++文件"""
|
|
||||||
if exclude_dirs is None:
|
|
||||||
exclude_dirs = ['.git', 'venv', '__pycache__'] # 默认排除的目录
|
|
||||||
|
|
||||||
c_files = []
|
|
||||||
for root, dirs, files in os.walk(directory):
|
|
||||||
# 跳过排除目录
|
|
||||||
dirs[:] = [d for d in dirs if d not in exclude_dirs]
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(('.c', '.h')):
|
|
||||||
full_path = os.path.join(root, file)
|
|
||||||
c_files.append(full_path)
|
|
||||||
return c_files
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""main function"""
|
|
||||||
target_dir = input("请输入要扫描的目录路径(留空为当前目录): ") or '.'
|
|
||||||
|
|
||||||
files = scan_files(target_dir)
|
|
||||||
total_lines = 0
|
|
||||||
|
|
||||||
print("\n统计结果:")
|
|
||||||
for idx, file in enumerate(files, 1):
|
|
||||||
lines = count_lines(file)
|
|
||||||
total_lines += lines
|
|
||||||
print(f"{idx:4d}. {file} ({lines} 行)")
|
|
||||||
|
|
||||||
print(f"\n总计: {len(files)} 个C/C++文件,共 {total_lines} 行代码")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user