Files
BrowserOS/build/context.py
2025-08-11 11:46:10 -07:00

242 lines
9.6 KiB
Python

#!/usr/bin/env python3
"""
Build context dataclass to hold all build state
"""
import time
import sys
from pathlib import Path
from dataclasses import dataclass
from utils import (
log_info, log_error, log_success, log_warning,
get_platform, get_platform_arch, get_executable_extension,
get_app_extension, normalize_path, join_paths,
IS_WINDOWS, IS_MACOS
)
@dataclass
class BuildContext:
"""Simple dataclass to hold all build state"""
root_dir: Path
chromium_src: Path = Path()
out_dir: str = "out/Default"
architecture: str = "" # Will be set in __post_init__
build_type: str = "debug"
apply_patches: bool = False
sign_package: bool = False
package: bool = False
build: bool = False
chromium_version: str = ""
nxtscape_version: str = ""
nxtscape_chromium_version: str = ""
start_time: float = 0.0
# App names - will be set based on platform
CHROMIUM_APP_NAME: str = ""
NXTSCAPE_APP_NAME: str = ""
NXTSCAPE_APP_BASE_NAME: str = "BrowserOS" # Base name without extension
# Third party
SPARKLE_VERSION: str = "2.7.0"
def __post_init__(self):
"""Load version files and set platform/architecture-specific configurations"""
# Set platform-specific defaults
if not self.architecture:
self.architecture = get_platform_arch()
# Set platform-specific app names
if IS_WINDOWS:
self.CHROMIUM_APP_NAME = f"chrome{get_executable_extension()}"
self.NXTSCAPE_APP_NAME = f"{self.NXTSCAPE_APP_BASE_NAME}{get_executable_extension()}"
elif IS_MACOS:
self.CHROMIUM_APP_NAME = "Chromium.app"
self.NXTSCAPE_APP_NAME = f"{self.NXTSCAPE_APP_BASE_NAME}.app"
else:
self.CHROMIUM_APP_NAME = "chrome"
self.NXTSCAPE_APP_NAME = self.NXTSCAPE_APP_BASE_NAME.lower()
# Set architecture-specific output directory with platform separator
if IS_WINDOWS:
self.out_dir = f"out\\Default_{self.architecture}"
else:
self.out_dir = f"out/Default_{self.architecture}"
version_dict = {}
if not self.chromium_version:
# Read from VERSION file
version_file = join_paths(self.root_dir, "CHROMIUM_VERSION")
if version_file.exists():
# Parse VERSION file format: MAJOR=137\nMINOR=0\nBUILD=7151\nPATCH=69
for line in version_file.read_text().strip().split("\n"):
key, value = line.split("=")
version_dict[key] = value
# Construct chromium_version as MAJOR.MINOR.BUILD.PATCH
self.chromium_version = f"{version_dict['MAJOR']}.{version_dict['MINOR']}.{version_dict['BUILD']}.{version_dict['PATCH']}"
if not self.nxtscape_version:
# Read from NXTSCAPE_VERSION file
version_file = join_paths(self.root_dir, "build", "config", "NXTSCAPE_VERSION")
if version_file.exists():
self.nxtscape_version = version_file.read_text().strip()
# Set nxtscape_chromium_version as chromium version with BUILD + nxtscape_version
if self.chromium_version and self.nxtscape_version and version_dict:
# Calculate new BUILD number by adding nxtscape_version to original BUILD
new_build = int(version_dict["BUILD"]) + int(self.nxtscape_version)
self.nxtscape_chromium_version = f"{version_dict['MAJOR']}.{version_dict['MINOR']}.{new_build}.{version_dict['PATCH']}"
# Determine chromium source directory
if self.chromium_src and self.chromium_src.exists():
log_warning(f"📁 Using provided Chromium source: {self.chromium_src}")
else:
log_warning(f"⚠️ Provided path does not exist: {self.chromium_src}")
self.chromium_src = join_paths(self.root_dir, "chromium_src")
if not self.chromium_src.exists():
log_error(
f"⚠️ Default Chromium source path does not exist: {self.chromium_src}"
)
raise FileNotFoundError(
f"Chromium source path does not exist: {self.chromium_src}"
)
self.start_time = time.time()
# Path getter methods
def get_config_dir(self) -> Path:
"""Get build config directory"""
return join_paths(self.root_dir, "build", "config")
def get_gn_config_dir(self) -> Path:
"""Get GN config directory"""
return join_paths(self.get_config_dir(), "gn")
def get_gn_flags_file(self) -> Path:
"""Get GN flags file for current build type"""
platform = get_platform()
return join_paths(self.get_gn_config_dir(), f"flags.{platform}.{self.build_type}.gn")
def get_copy_resources_config(self) -> Path:
"""Get copy resources configuration file"""
return join_paths(self.get_config_dir(), "copy_resources.yaml")
def get_patches_dir(self) -> Path:
"""Get patches directory"""
return join_paths(self.root_dir, "patches")
def get_nxtscape_patches_dir(self) -> Path:
"""Get Nxtscape specific patches directory"""
return join_paths(self.get_patches_dir(), "browseros")
def get_sparkle_dir(self) -> Path:
"""Get Sparkle directory"""
return join_paths(self.chromium_src, "third_party", "sparkle")
def get_sparkle_url(self) -> str:
"""Get Sparkle download URL"""
return f"https://github.com/sparkle-project/Sparkle/releases/download/{self.SPARKLE_VERSION}/Sparkle-{self.SPARKLE_VERSION}.tar.xz"
def get_resources_dir(self) -> Path:
"""Get resources directory"""
return join_paths(self.root_dir, "resources")
def get_resources_files_dir(self) -> Path:
"""Get resources files directory"""
return join_paths(self.get_resources_dir(), "files")
def get_resources_gen_dir(self) -> Path:
"""Get generated resources directory"""
return join_paths(self.get_resources_dir(), "gen")
def get_chrome_resources_dir(self) -> Path:
"""Get Chrome browser resources directory"""
return join_paths(self.chromium_src, "chrome", "browser", "resources")
def get_chrome_theme_dir(self) -> Path:
"""Get Chrome theme directory"""
return join_paths(self.chromium_src, "chrome", "app", "theme", "chromium")
def get_chrome_app_dir(self) -> Path:
"""Get Chrome app directory"""
return join_paths(self.chromium_src, "chrome", "app")
def get_entitlements_dir(self) -> Path:
"""Get entitlements directory"""
return join_paths(self.root_dir, "resources", "entitlements")
def get_dmg_dir(self) -> Path:
"""Get DMG output directory (macOS only)"""
return join_paths(self.chromium_src, self.out_dir, "dmg")
def get_pkg_dmg_path(self) -> Path:
"""Get pkg-dmg tool path (macOS only)"""
return join_paths(self.chromium_src, "chrome", "installer", "mac", "pkg-dmg")
def get_app_path(self) -> Path:
"""Get built app path"""
# For debug builds, check if the app has a different name
if self.build_type == "debug" and IS_MACOS:
# Check for debug-branded app name
debug_app_name = f"{self.NXTSCAPE_APP_BASE_NAME} Dev.app"
debug_app_path = join_paths(self.chromium_src, self.out_dir, debug_app_name)
if debug_app_path.exists():
return debug_app_path
return join_paths(self.chromium_src, self.out_dir, self.NXTSCAPE_APP_NAME)
def get_chromium_app_path(self) -> Path:
"""Get original Chromium app path"""
return join_paths(self.chromium_src, self.out_dir, self.CHROMIUM_APP_NAME)
def get_gn_args_file(self) -> Path:
"""Get GN args file path"""
return join_paths(self.chromium_src, self.out_dir, "args.gn")
def get_notarization_zip(self) -> Path:
"""Get notarization zip path (macOS only)"""
return join_paths(self.chromium_src, self.out_dir, "notarize.zip")
def get_dmg_name(self, signed=False) -> str:
"""Get DMG filename with architecture suffix"""
if self.architecture == "universal":
if signed:
return f"{self.NXTSCAPE_APP_BASE_NAME}_{self.nxtscape_chromium_version}_universal_signed.dmg"
return f"{self.NXTSCAPE_APP_BASE_NAME}_{self.nxtscape_chromium_version}_universal.dmg"
else:
if signed:
return f"{self.NXTSCAPE_APP_BASE_NAME}_{self.nxtscape_chromium_version}_{self.architecture}_signed.dmg"
return f"{self.NXTSCAPE_APP_BASE_NAME}_{self.nxtscape_chromium_version}_{self.architecture}.dmg"
def get_nxtscape_chromium_version(self) -> str:
"""Get Nxtscape version string"""
return self.nxtscape_chromium_version
def get_nxtscape_version(self) -> str:
"""Get Nxtscape version string"""
return self.nxtscape_version
def get_app_base_name(self) -> str:
"""Get app base name without extension"""
return self.NXTSCAPE_APP_BASE_NAME
# Extension names
def get_ai_extensions(self) -> list[str]:
"""Get list of AI extension names"""
return ["ai_side_panel"]
# Bundle identifiers
def get_bundle_identifier(self) -> str:
"""Get main bundle identifier"""
return "com.browseros.BrowserOS"
def get_base_identifier(self) -> str:
"""Get base identifier for components"""
return "com.browseros"
def get_dist_dir(self) -> Path:
"""Get distribution output directory with version"""
return join_paths(self.root_dir, "dist", self.nxtscape_version)