Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions QUICK_START.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ buckyos-build --help
buckyos-build --no-build-web-modules # 跳过 web modules 构建
buckyos-build --no-install # 不安装
buckyos-build --target=x86_64-unknown-linux-musl # 指定目标
buckyos-build --app demo_app # 只构建指定 app 使用的模块
buckyos-build --app app1 app2 # 同时构建多个 app 使用的模块
buckyos-build --timings # 生成 Cargo timings 报告
buckyos-build --timings-dir ./timings # 生成并复制 Cargo timings 报告
```

### 2. `buckyos-install` - 安装工具
Expand Down
12 changes: 12 additions & 0 deletions USAGE_EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ buckyos-build --no-install
# 指定目标平台
buckyos-build --target=x86_64-unknown-linux-musl

# 只构建指定 app 使用的模块
buckyos-build --app demo_app

# 支持同时指定多个 app
buckyos-build --app demo_app admin_app

# 生成 Cargo timings 报告
buckyos-build --timings

# 生成并复制 Cargo timings 报告到指定目录
buckyos-build --timings-dir ./timings

# 构建 amd64 版本
buckyos-build amd64

Expand Down
137 changes: 126 additions & 11 deletions src/build.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
import platform
from pathlib import Path
from typing import Optional

from .build_web_apps import build_web_modules
Expand Down Expand Up @@ -100,9 +101,15 @@ def _run(stdscr):
}
return selected

def _prompt_select_modules(project: BuckyProject, skip_web_module: bool) -> set[str]:
def _prompt_select_modules(
project: BuckyProject,
skip_web_module: bool,
selectable_modules: set[str] | None = None,
) -> set[str]:
selectable = []
for module_name, module_info in project.modules.items():
if selectable_modules is not None and module_name not in selectable_modules:
continue
if skip_web_module and isinstance(module_info, WebModuleInfo):
continue
if isinstance(module_info, WebModuleInfo):
Expand All @@ -124,24 +131,69 @@ def _prompt_select_modules(project: BuckyProject, skip_web_module: bool) -> set[

return _prompt_select_modules_line(selectable)

def build(project: BuckyProject, rust_target: str, skip_web_module: bool, selected_modules: set[str] | None = None):
def _split_option_values(value: str) -> list[str]:
return [item for item in value.replace(",", " ").split() if item]

def _collect_app_modules(project: BuckyProject, app_names: list[str]) -> set[str]:
selected_modules: set[str] = set()
missing_apps = []
skipped_modules: set[str] = set()

for app_name in app_names:
app_info = project.apps.get(app_name)
if app_info is None:
missing_apps.append(app_name)
continue

for module_name in app_info.modules.keys():
if module_name in project.modules:
selected_modules.add(module_name)
else:
skipped_modules.add(module_name)

if missing_apps:
raise ValueError(f"App not found in bucky_project.apps: {', '.join(sorted(missing_apps))}")

if skipped_modules:
print(
"Warning: app modules not found in bucky_project.modules, skipped: "
+ ", ".join(sorted(skipped_modules))
)

return selected_modules

def build(
project: BuckyProject,
rust_target: str,
skip_web_module: bool,
selected_modules: set[str] | None = None,
timings: bool = False,
timings_dir: Path | None = None,
):
if not skip_web_module:
build_web_modules(project, None if selected_modules is None else list(selected_modules))
build_rust_modules(project, rust_target, None if selected_modules is None else list(selected_modules))
build_rust_modules(
project,
rust_target,
None if selected_modules is None else list(selected_modules),
timings,
timings_dir,
)
copy_build_results(project, skip_web_module, rust_target, None if selected_modules is None else list(selected_modules))

def build_main():
from pathlib import Path


def build_main():
skip_web_module = False
system = platform.system() # Linux / Windows / Darwin
arch = platform.machine() # x86_64 / AMD64 / arm64 / arm
print(f"DEBUG: system:{system},arch:{arch}")
target = ""
select_mode = False
selected_modules = None
if system == "Linux" and (arch == "x86_64" or arch == "AMD64"):
target = "x86_64-unknown-linux-musl"
app_names: list[str] = []
timings = False
timings_dir: Path | None = None
if system == "Linux" and (arch == "x86_64" or arch == "AMD64"):
target = "x86_64-unknown-linux-musl"
elif system == "Windows" and (arch == "x86_64" or arch == "AMD64"):
target = "x86_64-pc-windows-msvc"
# elif system == "Linux" and (arch == "x86_64" or arch == "AMD64"):
Expand All @@ -157,6 +209,47 @@ def build_main():
skip_web_module = True
i += 1
continue
if arg == "--timings":
timings = True
i += 1
continue
if arg == "--timings-dir":
if i + 1 >= len(args) or args[i + 1].startswith("-"):
print("Error: --timings-dir requires a directory")
sys.exit(1)
timings = True
timings_dir = Path(args[i + 1])
i += 2
continue
if arg.startswith("--timings-dir="):
value = arg.split("=", 1)[1]
if not value:
print("Error: --timings-dir requires a directory")
sys.exit(1)
timings = True
timings_dir = Path(value)
i += 1
continue
if arg == "--app":
apps = []
j = i + 1
while j < len(args) and not args[j].startswith("-"):
apps.extend(_split_option_values(args[j]))
j += 1
if not apps:
print("Error: --app requires at least one app name")
sys.exit(1)
app_names.extend(apps)
i = j
continue
if arg.startswith("--app="):
apps = _split_option_values(arg.split("=", 1)[1])
if not apps:
print("Error: --app requires at least one app name")
sys.exit(1)
app_names.extend(apps)
i += 1
continue
if arg == "--select" or arg == "-s":
# Examples: "-s mod_a mod_b" builds those modules; "-s" enters interactive selection.
modules = []
Expand Down Expand Up @@ -202,14 +295,36 @@ def build_main():
overlay_files.append(local_config_file)
bucky_project = BuckyProject.from_file(config_file, overlay_files)

app_selected_modules = None
if app_names:
try:
app_selected_modules = _collect_app_modules(bucky_project, app_names)
except ValueError as e:
print(f"Error: {e}")
sys.exit(1)

if not app_selected_modules:
print("No modules selected by --app; build skipped.")
return

if selected_modules is None and select_mode:
selected_modules = _prompt_select_modules(bucky_project, skip_web_module)
selected_modules = _prompt_select_modules(bucky_project, skip_web_module, app_selected_modules)
if not selected_modules:
print("No modules selected; build skipped.")
return

if app_selected_modules is not None:
if selected_modules is None:
selected_modules = app_selected_modules
else:
selected_modules = selected_modules.intersection(app_selected_modules)
if not selected_modules:
print("No modules selected after applying --app filter; build skipped.")
return
print(f"Selected modules from --app: {', '.join(sorted(selected_modules))}")

print(f"Rust target is : {target}")
build(bucky_project, target, skip_web_module, selected_modules)
build(bucky_project, target, skip_web_module, selected_modules, timings, timings_dir)

if __name__ == "__main__":
build_main()
88 changes: 72 additions & 16 deletions src/build_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import subprocess
import platform
import shutil
import re
import re
from datetime import datetime
from pathlib import Path
from typing import Optional,Dict

from .project import BuckyProject, RustModuleInfo
Expand Down Expand Up @@ -402,9 +403,55 @@ def get_cross_compile_env_vars_by_target(target: str) -> Optional[Dict[str, str]

return env_vars

def build_rust_modules(project: BuckyProject, rust_target: str, selected_modules: list[str] | None = None):
print(f"🚀 Building Rust code,target_dir is {project.rust_target_dir},target is {rust_target}")
env = os.environ.copy()
def _resolve_rust_target_dir(project: BuckyProject) -> Path:
target_dir = Path(project.rust_target_dir)
if target_dir.is_absolute():
return target_dir
return project.resolve_from_base_dir(target_dir)

def _get_timing_reports(target_dir: Path) -> list[Path]:
cargo_timings_dir = target_dir / "cargo-timings"
if not cargo_timings_dir.exists():
return []
return sorted(
cargo_timings_dir.glob("cargo-timing*.html"),
key=lambda path: path.stat().st_mtime_ns,
)

def _resolve_timings_output_dir(project: BuckyProject, timings_dir: str | Path) -> Path:
output_dir = Path(os.path.expanduser(os.path.expandvars(os.fspath(timings_dir))))
if output_dir.is_absolute():
return output_dir
return project.resolve_from_base_dir(output_dir)

def _copy_timing_reports(
project: BuckyProject,
target_dir: Path,
timings_dir: str | Path,
previous_reports: list[Path],
) -> None:
previous_report_set = set(previous_reports)
reports = [path for path in _get_timing_reports(target_dir) if path not in previous_report_set]
if not reports:
print(f"Warning: no new Cargo timings report found under {target_dir / 'cargo-timings'}")
return

output_dir = _resolve_timings_output_dir(project, timings_dir)
output_dir.mkdir(parents=True, exist_ok=True)
for report in reports:
target_file = output_dir / report.name
shutil.copy2(report, target_file)
print(f"Copied Cargo timings report: {report} => {target_file}")

def build_rust_modules(
project: BuckyProject,
rust_target: str,
selected_modules: list[str] | None = None,
timings: bool = False,
timings_dir: str | Path | None = None,
):
print(f"🚀 Building Rust code,target_dir is {project.rust_target_dir},target is {rust_target}")
env = os.environ.copy()
build_env = get_build_metadata(str(project.base_dir))
for key, value in build_env.items():
env.setdefault(key, value)
Expand All @@ -417,11 +464,17 @@ def build_rust_modules(project: BuckyProject, rust_target: str, selected_modules
env_vars = get_env_vars_by_target(rust_target)
env.update(env_vars)

cross_compile_env_vars = get_cross_compile_env_vars_by_target(rust_target)
cargo_args = ["cargo", "build", "--release", "--target-dir", str(project.rust_target_dir)]
if selected_modules is not None:
rust_modules = [
module_name
target_dir = _resolve_rust_target_dir(project)
timings = timings or timings_dir is not None
previous_timing_reports = _get_timing_reports(target_dir) if timings_dir is not None else []

cross_compile_env_vars = get_cross_compile_env_vars_by_target(rust_target)
cargo_args = ["cargo", "build", "--release", "--target-dir", str(target_dir)]
if timings:
cargo_args.append("--timings")
if selected_modules is not None:
rust_modules = [
module_name
for module_name, module_info in project.modules.items()
if isinstance(module_info, RustModuleInfo)
]
Expand Down Expand Up @@ -454,11 +507,14 @@ def build_rust_modules(project: BuckyProject, rust_target: str, selected_modules
cwd=project.base_dir,
env=env)
else:
print("*", " ".join(cargo_args))
subprocess.run(cargo_args,
check=True,
cwd=project.base_dir,
env=env)

print(f'✅ Build Rust Modules completed')
print("*", " ".join(cargo_args))
subprocess.run(cargo_args,
check=True,
cwd=project.base_dir,
env=env)

if timings_dir is not None:
_copy_timing_reports(project, target_dir, timings_dir, previous_timing_reports)

print(f'✅ Build Rust Modules completed')

Loading