Files
BrowserOS/packages/browseros/build/modules/apply/apply_feature.py
Nikhil ffb1dfdf2a cli improvements (#222)
* feat: support annotate

* feat: extract supports --feature

* feat: support classify in dev cli
2025-12-06 17:04:00 -08:00

142 lines
4.2 KiB
Python
Generated

"""
Apply Feature - Apply patches for a specific feature.
"""
import yaml
from typing import List, Tuple, Optional
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
from ...common.utils import log_info, log_error, log_warning, log_success
from .common import process_patch_list
def apply_feature_patches(
build_ctx: Context,
feature_name: str,
dry_run: bool = False,
reset_to: Optional[str] = None,
) -> Tuple[int, List[str]]:
"""Apply patches for a specific feature.
Args:
build_ctx: Build context
feature_name: Name of the feature
dry_run: Only check if patches would apply
reset_to: Commit to reset files to before applying (optional)
Returns:
Tuple of (applied_count, failed_list)
"""
# Load features.yaml
features_path = build_ctx.get_features_yaml_path()
if not features_path.exists():
log_error("No features.yaml found")
return 0, []
with open(features_path) as f:
data = yaml.safe_load(f)
features = data.get("features", {})
if feature_name not in features:
log_error(f"Feature '{feature_name}' not found")
log_info("Available features:")
for name in features:
log_info(f" - {name}")
return 0, []
file_list = features[feature_name].get("files", [])
if not file_list:
log_warning(f"Feature '{feature_name}' has no files")
return 0, []
log_info(f"Applying patches for feature '{feature_name}' ({len(file_list)} files)")
if dry_run:
log_info("DRY RUN - No changes will be made")
# Create patch list
patches_dir = build_ctx.get_patches_dir()
patch_list = []
for file_path in file_list:
patch_path = build_ctx.get_patch_path_for_file(file_path)
patch_list.append((patch_path, file_path))
# Process patches
applied, failed = process_patch_list(
patch_list,
build_ctx.chromium_src,
patches_dir,
dry_run,
interactive=False, # Feature patches don't support interactive mode
reset_to=reset_to,
)
# Summary
log_info(f"\nSummary: {applied} applied, {len(failed)} failed")
if failed:
log_error("Failed patches:")
for p in failed:
log_error(f" - {p}")
return applied, failed
class ApplyFeatureModule(CommandModule):
"""Apply patches for a specific feature"""
produces = []
requires = []
description = "Apply patches for a specific feature"
def validate(self, ctx: Context) -> None:
"""Validate git is available"""
import shutil
if not shutil.which("git"):
raise ValidationError("Git is not available in PATH")
if not ctx.chromium_src.exists():
raise ValidationError(f"Chromium source not found: {ctx.chromium_src}")
def execute(
self,
ctx: Context,
feature_name: str,
interactive: bool = True,
reset_to: Optional[str] = None,
annotate: bool = False,
**kwargs,
) -> None:
"""Execute apply feature patches
Args:
feature_name: Name of the feature to apply
interactive: Interactive mode (ask before each patch)
reset_to: Commit to reset files to before applying (optional)
annotate: Create git commit for this feature after applying
"""
applied, failed = apply_feature_patches(
ctx,
feature_name,
dry_run=False,
reset_to=reset_to,
)
if failed:
raise RuntimeError(
f"Failed to apply {len(failed)} patches for feature '{feature_name}'"
)
# Run annotate for this specific feature if requested
if annotate:
from ..annotate import annotate_single_feature
log_info("\n" + "=" * 60)
log_info(f"🏗️ Creating commit for feature '{feature_name}'...")
if annotate_single_feature(ctx, feature_name):
log_success(f"✓ Created commit for '{feature_name}'")
else:
log_info("No commit created (no modified files found)")