mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 23:53:25 +00:00
315 lines
9.4 KiB
Python
315 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Universal binary merge module for Nxtscape Browser
|
|
Provides functions to merge two architecture builds into a universal binary
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import List
|
|
from context import BuildContext
|
|
from utils import run_command, log_info, log_error, log_success, log_warning
|
|
|
|
|
|
def merge_architectures(
|
|
arch1_path: Path,
|
|
arch2_path: Path,
|
|
output_path: Path,
|
|
universalizer_script: Path = None,
|
|
) -> bool:
|
|
"""
|
|
Merge two architecture builds into a universal binary
|
|
|
|
Args:
|
|
arch1_path: Path to first architecture .app bundle
|
|
arch2_path: Path to second architecture .app bundle
|
|
output_path: Path where universal .app bundle should be created
|
|
universalizer_script: Path to universalizer script (optional)
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
log_info("🔄 Merging architecture builds into universal binary...")
|
|
|
|
# Validate input paths
|
|
if not arch1_path.exists():
|
|
log_error(f"Architecture 1 app not found: {arch1_path}")
|
|
return False
|
|
|
|
if not arch2_path.exists():
|
|
log_error(f"Architecture 2 app not found: {arch2_path}")
|
|
return False
|
|
|
|
log_info(f"📱 Input 1: {arch1_path}")
|
|
log_info(f"📱 Input 2: {arch2_path}")
|
|
log_info(f"🎯 Output: {output_path}")
|
|
|
|
# Find universalizer script
|
|
if universalizer_script is None:
|
|
# Try to find it relative to this module
|
|
current_dir = Path(__file__).parent.parent
|
|
universalizer_script = current_dir / "universalizer_patched.py"
|
|
|
|
if not universalizer_script.exists():
|
|
log_error(f"Universalizer script not found: {universalizer_script}")
|
|
return False
|
|
|
|
# Create output directory if needed
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Remove existing output if present
|
|
if output_path.exists():
|
|
log_info(f"Removing existing output: {output_path}")
|
|
shutil.rmtree(output_path)
|
|
|
|
try:
|
|
# Run universalizer
|
|
cmd = [
|
|
sys.executable,
|
|
str(universalizer_script),
|
|
str(arch1_path),
|
|
str(arch2_path),
|
|
str(output_path),
|
|
]
|
|
|
|
log_info(f"Running universalizer...")
|
|
log_info(f"Command: {' '.join(cmd)}")
|
|
run_command(cmd)
|
|
|
|
if output_path.exists():
|
|
log_success(f"Universal binary created: {output_path}")
|
|
return True
|
|
else:
|
|
log_error("Universal binary creation failed - output not found")
|
|
return False
|
|
|
|
except Exception as e:
|
|
log_error(f"Failed to create universal binary: {e}")
|
|
return False
|
|
|
|
|
|
def create_minimal_context(
|
|
app_path: Path, chromium_src: Path, root_dir: Path, architecture: str = "universal"
|
|
) -> BuildContext:
|
|
"""Create a minimal BuildContext for signing/packaging operations"""
|
|
|
|
out_dir_path = app_path.parent # out/Default_universal
|
|
|
|
log_info(f"Creating context from app path: {app_path}")
|
|
log_info(f" Out dir: {out_dir_path}")
|
|
log_info(f" Chromium src: {chromium_src}")
|
|
log_info(f" Root dir: {root_dir}")
|
|
|
|
ctx = BuildContext(
|
|
root_dir=root_dir,
|
|
chromium_src=chromium_src,
|
|
architecture=architecture,
|
|
build_type="release", # Assume release for universal builds
|
|
apply_patches=False,
|
|
sign_package=True,
|
|
package=True,
|
|
build=False,
|
|
)
|
|
|
|
# Override out_dir to match the actual location
|
|
ctx.out_dir = out_dir_path.name
|
|
|
|
# Override get_app_path to return the actual app path for merge operations
|
|
def get_app_path_override():
|
|
return app_path
|
|
|
|
ctx.get_app_path = get_app_path_override
|
|
|
|
log_info(f"Context created with out_dir: {ctx.out_dir}")
|
|
log_info(f"App path: {ctx.get_app_path()}")
|
|
log_info(f"PKG-DMG path: {ctx.get_pkg_dmg_path()}")
|
|
|
|
return ctx
|
|
|
|
|
|
def merge_sign_package(
|
|
arch1_path: Path,
|
|
arch2_path: Path,
|
|
output_path: Path,
|
|
chromium_src: Path,
|
|
root_dir: Path,
|
|
sign: bool = True,
|
|
package: bool = True,
|
|
universalizer_script: Path = None,
|
|
) -> bool:
|
|
"""
|
|
Complete workflow: merge, sign, and package universal binary
|
|
|
|
Args:
|
|
arch1_path: Path to first architecture .app bundle
|
|
arch2_path: Path to second architecture .app bundle
|
|
output_path: Path where universal .app bundle should be created
|
|
chromium_src: Path to chromium source directory
|
|
root_dir: Path to project root directory
|
|
sign: Whether to sign the universal binary
|
|
package: Whether to create DMG package
|
|
universalizer_script: Path to universalizer script (optional)
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
log_info("=" * 70)
|
|
log_info("🚀 Starting merge, sign, and package workflow...")
|
|
log_info("=" * 70)
|
|
|
|
# Step 1: Merge architectures
|
|
if not merge_architectures(
|
|
arch1_path, arch2_path, output_path, universalizer_script
|
|
):
|
|
return False
|
|
|
|
# Step 2: Sign (if requested)
|
|
if sign:
|
|
log_info("\n" + "=" * 70)
|
|
log_info("🔏 Signing universal binary...")
|
|
log_info("=" * 70)
|
|
|
|
try:
|
|
from modules.sign import sign_app
|
|
|
|
ctx = create_minimal_context(output_path, chromium_src, root_dir)
|
|
if not sign_app(ctx, create_dmg=False):
|
|
log_error("Failed to sign universal binary")
|
|
return False
|
|
|
|
log_success("Universal binary signed successfully!")
|
|
|
|
except ImportError as e:
|
|
log_error(f"Could not import signing module: {e}")
|
|
return False
|
|
except Exception as e:
|
|
log_error(f"Signing failed: {e}")
|
|
return False
|
|
|
|
# Step 3: Package (if requested)
|
|
if package:
|
|
log_info("\n" + "=" * 70)
|
|
log_info("📦 Creating DMG package...")
|
|
log_info("=" * 70)
|
|
|
|
try:
|
|
from modules.package import create_dmg
|
|
|
|
ctx = create_minimal_context(output_path, chromium_src, root_dir)
|
|
|
|
# Create DMG in parent directory
|
|
dmg_dir = ctx.root_dir / "dmg"
|
|
dmg_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
dmg_name = ctx.get_dmg_name()
|
|
|
|
dmg_path = dmg_dir / dmg_name
|
|
pkg_dmg_path = ctx.get_pkg_dmg_path()
|
|
|
|
# pkg-dmg should now be available since we enforce chromium-src path
|
|
if not pkg_dmg_path.exists():
|
|
log_error(f"Chromium pkg-dmg not found at: {pkg_dmg_path}")
|
|
log_error("Make sure you provided the correct --chromium-src path")
|
|
return False
|
|
|
|
if create_dmg(output_path, dmg_path, "BrowserOS", pkg_dmg_path):
|
|
log_success(f"DMG created: {dmg_name}")
|
|
else:
|
|
log_error("Failed to create DMG")
|
|
return False
|
|
|
|
except ImportError as e:
|
|
log_error(f"Could not import packaging module: {e}")
|
|
return False
|
|
except Exception as e:
|
|
log_error(f"Packaging failed: {e}")
|
|
return False
|
|
|
|
log_info("\n" + "=" * 70)
|
|
log_success("Merge, sign, and package workflow completed successfully!")
|
|
log_info("=" * 70)
|
|
|
|
return True
|
|
|
|
|
|
def handle_merge_command(
|
|
arch1_path: Path,
|
|
arch2_path: Path,
|
|
chromium_src: Path,
|
|
sign: bool = False,
|
|
package: bool = False,
|
|
) -> bool:
|
|
"""
|
|
Handle the merge command from CLI
|
|
|
|
Args:
|
|
arch1_path: Path to first architecture .app bundle
|
|
arch2_path: Path to second architecture .app bundle
|
|
chromium_src: Path to chromium source directory
|
|
sign: Whether to sign the universal binary
|
|
package: Whether to create DMG package
|
|
|
|
Returns:
|
|
True if successful, False otherwise
|
|
"""
|
|
log_info("🔄 Running merge command...")
|
|
log_info(f" Arch 1: {arch1_path}")
|
|
log_info(f" Arch 2: {arch2_path}")
|
|
log_info(f" Sign: {sign}")
|
|
log_info(f" Package: {package}")
|
|
log_info(f"📁 Using Chromium source: {chromium_src}")
|
|
|
|
# Validate input paths exist
|
|
if not arch1_path.exists():
|
|
log_error(f"Architecture 1 app not found: {arch1_path}")
|
|
return False
|
|
|
|
if not arch2_path.exists():
|
|
log_error(f"Architecture 2 app not found: {arch2_path}")
|
|
return False
|
|
|
|
# Get root_dir from where this module is located
|
|
root_dir = Path(__file__).parent.parent.parent
|
|
log_info(f"📂 Using root directory: {root_dir}")
|
|
|
|
# Auto-generate output path in chromium source
|
|
# Get the app name from BuildContext
|
|
from context import BuildContext
|
|
|
|
temp_ctx = BuildContext(
|
|
root_dir=root_dir,
|
|
chromium_src=chromium_src,
|
|
architecture="universal",
|
|
build_type="release",
|
|
)
|
|
output_path = (
|
|
chromium_src / "out" / "Default_universal" / temp_ctx.NXTSCAPE_APP_NAME
|
|
)
|
|
log_info(f" Output: {output_path} (auto-generated)")
|
|
|
|
try:
|
|
success = merge_sign_package(
|
|
arch1_path=arch1_path,
|
|
arch2_path=arch2_path,
|
|
output_path=output_path,
|
|
chromium_src=chromium_src,
|
|
root_dir=root_dir,
|
|
sign=sign,
|
|
package=package,
|
|
)
|
|
|
|
if success:
|
|
log_success("Merge command completed successfully!")
|
|
else:
|
|
log_error("Merge command failed!")
|
|
|
|
return success
|
|
except Exception as e:
|
|
log_error(f"Merge command failed with exception: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
return False
|