From bf245006b230ce5b886942e951565ed6448b1d94 Mon Sep 17 00:00:00 2001 From: daijro Date: Thu, 1 Aug 2024 20:33:46 -0500 Subject: [PATCH] multibuild: Allow multiple build targets, Makefile changes, more. - Allow multiple OS & arch build targets to be passed in multibuild.py - Add make set-target to change target OS & arch - Cleanup/refactor patch.py and package.py, move common functions to mixin file --- Makefile | 22 ++-- README.md | 44 +++++-- launcher/build.sh | 10 +- multibuild.py | 110 ++++++++++++------ scripts/_mixin.py | 133 +++++++++++++++++++++ scripts/developer.py | 36 ++---- scripts/package.py | 70 ++++++++---- scripts/patch.py | 267 +++++++++++++++---------------------------- 8 files changed, 410 insertions(+), 282 deletions(-) create mode 100644 scripts/_mixin.py diff --git a/Makefile b/Makefile index 4937b7f..e54c8bd 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ debs := python3 python3-dev python3-pip p7zip-full golang-go msitools wget aria2 rpms := python3 python3-devel p7zip golang msitools wget aria2c pacman := python python-pip p7zip go msitools wget aria2c -.PHONY: help fetch setup setup-minimal clean distclean build package build-launcher check-arch revert edits run bootstrap mozbootstrap dir package-common package-linux package-macos package-windows +.PHONY: help fetch setup setup-minimal clean set-target distclean build package build-launcher check-arch revert edits run bootstrap mozbootstrap dir package-linux package-macos package-windows help: @echo "Available targets:" @@ -23,6 +23,7 @@ help: @echo " clean - Remove build artifacts" @echo " distclean - Remove everything including downloads" @echo " build - Build Camoufox" + @echo " set-target - Change the build target with BUILD_TARGET" @echo " package-linux - Package Camoufox for Linux" @echo " package-macos - Package Camoufox for macOS" @echo " package-windows - Package Camoufox for Windows" @@ -63,6 +64,9 @@ dir: python3 scripts/patch.py $(version) $(release) touch $(cf_source_dir)/_READY +set-target: + python3 scripts/patch.py $(version) $(release) --mozconfig-only + mozbootstrap: cd $(cf_source_dir) && MOZBUILD_STATE_PATH=$$HOME/.mozbuild ./mach --no-interactive bootstrap --application-choice=browser @@ -93,19 +97,15 @@ edits: python ./scripts/developer.py check-arch: - @if [ "$(arch)" != "x64" ] && [ "$(arch)" != "x86" ] && [ "$(arch)" != "arm64" ]; then \ - echo "Error: Invalid arch value. Must be x64, x86, or arm64."; \ + @if ! echo "x86_64 i686 arm64" | grep -qw "$(arch)"; then \ + echo "Error: Invalid arch value. Must be x86_64, i686, or arm64."; \ exit 1; \ fi build-launcher: check-arch - cd launcher && ./build.sh $(arch) $(os) + cd launcher && bash build.sh $(arch) $(os) -package-common: check-arch - cd $(cf_source_dir) && cat browser/locales/shipped-locales | xargs ./mach package-multi-locale --locales - cp -v $(cf_source_dir)/obj-*/dist/camoufox-$(version)-$(release).*.* . - -package-linux: package-common +package-linux: make build-launcher arch=$(arch) os=linux; python3 scripts/package.py linux \ --includes \ @@ -116,7 +116,7 @@ package-linux: package-common --arch $(arch) \ --fonts windows macos linux -package-macos: package-common +package-macos: make build-launcher arch=$(arch) os=macos; python3 scripts/package.py macos \ --includes \ @@ -126,7 +126,7 @@ package-macos: package-common --arch $(arch) \ --fonts windows linux -package-windows: package-common +package-windows: make build-launcher arch=$(arch) os=windows; python3 scripts/package.py windows \ --includes \ diff --git a/README.md b/README.md index 08eeba7..d68f6be 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ Miscellaneous (WebGl spoofing, battery status, etc) #### Playwright support -- A more updated version of Playwright's Juggler for the latest Firefox, maintained by me +- Updated Playwright's Juggler for the latest Firefox - Various config patches to evade bot detection #### Debloat/Optimizations @@ -322,16 +322,33 @@ After that, you have to bootstrap your system to be able to build Camoufox. You make bootstrap ``` -Finally you can build Camoufox and then package it with the following commands: +Finally you can build and package Camoufox the following command: ```bash -make build -# Example package commands: -make package-linux arch=x64 -make package-windows arch=x86 -make package-macos arch=amd64 +python3 multibuild.py --target linux windows macos --arch x86_64 arm64 i686 ``` +
+ +CLI Parameters + + +```bash +Options: + -h, --help show this help message and exit + --target {linux,windows,macos} [{linux,windows,macos} ...] + Target platforms to build + --arch {x86_64,arm64,i686} [{x86_64,arm64,i686} ...] + Target architectures to build for each platform + --bootstrap Bootstrap the build system + --clean Clean the build directory before starting + +Example: +$ python3 multibuild.py --target linux windows macos --arch x86_64 arm64 +``` + +
+ ### Using Docker Camoufox can be built through Docker on all platforms. @@ -374,10 +391,15 @@ Docker CLI Parameters ```bash Options: -h, --help show this help message and exit - --target {linux,windows,macos} - Target platform for the build - --arch {x86_64,arm64,i686} - Target architecture for the build + --target {linux,windows,macos} [{linux,windows,macos} ...] + Target platforms to build + --arch {x86_64,arm64,i686} [{x86_64,arm64,i686} ...] + Target architectures to build for each platform + --bootstrap Bootstrap the build system + --clean Clean the build directory before starting + +Example: +$ docker run -v "$(pwd)/dist:/app/dist" camoufox-builder --target windows macos linux --arch x86_64 arm64 i686 ``` diff --git a/launcher/build.sh b/launcher/build.sh index 9bf839b..41b0ac6 100644 --- a/launcher/build.sh +++ b/launcher/build.sh @@ -2,7 +2,7 @@ if [ $# -ne 2 ]; then echo "Usage: $0 " - echo "arch: x64, x86, arm64" + echo "arch: x86_64, i686, arm64" echo "os: linux, windows, macos" exit 1 fi @@ -11,10 +11,10 @@ ARCH=$1 OS=$2 case $ARCH in - x64) GOARCH=amd64 ;; - x86) GOARCH=386 ;; - arm64) GOARCH=arm64 ;; - *) echo "Invalid architecture"; exit 1 ;; + x86_64) GOARCH=amd64 ;; + i686) GOARCH=386 ;; + arm64) GOARCH=arm64 ;; + *) echo "Invalid architecture"; exit 1 ;; esac case $OS in diff --git a/multibuild.py b/multibuild.py index e1d63a8..add5240 100644 --- a/multibuild.py +++ b/multibuild.py @@ -1,5 +1,18 @@ """ Easy build CLI for Camoufox + +options: + -h, --help show this help message and exit + --target {linux,windows,macos} [{linux,windows,macos} ...] + Target platforms to build + --arch {x86_64,arm64,i686} [{x86_64,arm64,i686} ...] + Target architectures to build for each platform + --bootstrap Bootstrap the build system + --clean Clean the build directory before starting + +Example: +$ python3 multibuild.py --target linux windows macos --arch x86_64 arm64 + Since Camoufox is NOT meant to be used as a daily driver, no installers are provided. """ @@ -15,11 +28,11 @@ AVAILABLE_TARGETS = ["linux", "windows", "macos"] AVAILABLE_ARCHS = ["x86_64", "arm64", "i686"] -def exec(cmd, exit_on_fail=True): +def run(cmd, exit_on_fail=True): print(f'\n------------\n{cmd}\n------------\n') retval = os.system(cmd) if retval != 0 and exit_on_fail: - print("fatal error: command '{}' failed".format(cmd)) + print(f"fatal error: command '{cmd}' failed") sys.exit(1) return retval @@ -29,51 +42,43 @@ class BSYS: target: str arch: str - def bootstrap(self): - exec('make bootstrap') + @staticmethod + def bootstrap(): + """Bootstrap the build system""" + run('make bootstrap') def build(self): + """Build the Camoufox source code""" os.environ['BUILD_TARGET'] = f'{self.target},{self.arch}' - exec(f'make build') + run('make build') def package(self): - if self.arch == 'x86_64': - _arch = 'x64' - else: - _arch = 'arm64' - exec(f'make package-{self.target} arch={_arch}') + """Package the Camoufox source code""" + run(f'make package-{self.target} arch={self.arch}') + + def update_target(self): + """Change the build target""" + os.environ['BUILD_TARGET'] = f'{self.target},{self.arch}' + run('make set-target') @property def assets(self) -> List[str]: - package_pattern = f'camoufox-*.en-US.*.zip' + """Get the list of assets""" + package_pattern = 'camoufox-*.en-US.*.zip' return glob.glob(package_pattern) - def clean(self): - exec('make clean') + @staticmethod + def clean(): + """Clean the Camoufox directory""" + run('make clean') -def main(): - parser = argparse.ArgumentParser(description="Easy build CLI for Camoufox") - parser.add_argument( - "--target", choices=AVAILABLE_TARGETS, required=True, help="Target platform for the build" - ) - parser.add_argument( - "--arch", choices=AVAILABLE_ARCHS, required=True, help="Target architecture for the build" - ) - parser.add_argument("--bootstrap", action="store_true", help="Bootstrap the build system") - parser.add_argument( - "--clean", action="store_true", help="Clean the build directory before starting" - ) - - args = parser.parse_args() - - builder = BSYS(args.target, args.arch) - # Run bootstrap if requested - if args.bootstrap: - builder.bootstrap() - # Clean if requested - if args.clean: - builder.clean() +def run_build(target, arch): + """ + Run the build for the given target and architecture + """ + builder = BSYS(target, arch) + builder.update_target() # Run build builder.build() # Run package @@ -83,5 +88,40 @@ def main(): os.rename(asset, f'dist/{asset}') +def main(): + parser = argparse.ArgumentParser(description="Easy build CLI for Camoufox") + parser.add_argument( + "--target", + choices=AVAILABLE_TARGETS, + nargs='+', + required=True, + help="Target platform for the build", + ) + parser.add_argument( + "--arch", + choices=AVAILABLE_ARCHS, + nargs='+', + required=True, + help="Target architecture for the build", + ) + parser.add_argument("--bootstrap", action="store_true", help="Bootstrap the build system") + parser.add_argument( + "--clean", action="store_true", help="Clean the build directory before starting" + ) + + args = parser.parse_args() + + # Run bootstrap if requested + if args.bootstrap: + BSYS.bootstrap() + # Clean if requested + if args.clean: + BSYS.clean() + # Run build + for target in args.target: + for arch in args.arch: + run_build(target, arch) + + if __name__ == "__main__": main() diff --git a/scripts/_mixin.py b/scripts/_mixin.py new file mode 100644 index 0000000..3b4e818 --- /dev/null +++ b/scripts/_mixin.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 + +""" +Common functions used across the Camoufox build system. +Not meant to be called directly. +""" + +import contextlib +import fnmatch +import optparse +import os +import sys +import time + +start_time = time.time() + + +@contextlib.contextmanager +def temp_cd(path): + """Temporarily change to a different working directory""" + _old_cwd = os.getcwd() + abs_path = os.path.abspath(path) + assert os.path.exists(abs_path), f'{abs_path} does not exist.' + os.chdir(abs_path) + + try: + yield + finally: + os.chdir(_old_cwd) + + +def get_options(): + """Get options""" + parser = optparse.OptionParser() + parser.add_option('--mozconfig-only', dest='mozconfig_only', default=False, action="store_true") + parser.add_option( + '-P', '--no-settings-pane', dest='settings_pane', default=True, action="store_false" + ) + return parser.parse_args() + + +def find_src_dir(root_dir='.', version=None, release=None): + """Get the source directory""" + if version and release: + name = os.path.join(root_dir, f'camoufox-{version}-{release}') + assert os.path.exists(name), f'{name} does not exist.' + return name + folders = os.listdir(root_dir) + for folder in folders: + if os.path.isdir(folder) and folder.startswith('camoufox-'): + return os.path.join(root_dir, folder) + raise FileNotFoundError('No camoufox-* folder found') + + +def get_moz_target(target, arch): + """Get moz_target from target and arch""" + if target == "linux": + return "aarch64-unknown-linux-gnu" if arch == "arm64" else f"{arch}-pc-linux-gnu" + if target == "windows": + return f"{arch}-pc-mingw32" + if target == "macos": + return "aarch64-apple-darwin" if arch == "arm64" else f"{arch}-apple-darwin" + raise ValueError(f"Unsupported target: {target}") + + +def list_files(root_dir, suffix): + """List files in a directory""" + for root, _, files in os.walk(root_dir): + for file in fnmatch.filter(files, suffix): + full_path = os.path.join(root, file) + relative_path = os.path.relpath(full_path, root_dir) + yield os.path.join(root_dir, relative_path).replace('\\', '/') + + +def list_patches(root_dir='../patches', suffix='*.patch'): + """List all patch files""" + return sorted(list_files(root_dir, suffix), key=os.path.basename) + + +def script_exit(statuscode): + """Exit the script""" + if (time.time() - start_time) > 60: + # print elapsed time + elapsed = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time)) + print(f"\n\aElapsed time: {elapsed}") + sys.stdout.flush() + + sys.exit(statuscode) + + +def run(cmd, exit_on_fail=True, do_print=True): + """Run a command""" + if not cmd: + return + if do_print: + print(cmd) + sys.stdout.flush() + retval = os.system(cmd) + if retval != 0 and exit_on_fail: + print(f"fatal error: command '{cmd}' failed") + sys.stdout.flush() + script_exit(1) + return retval + + +def patch(patchfile, reverse=False, silent=False): + """Run a patch file""" + if reverse: + cmd = f"patch -p1 -R -i {patchfile}" + else: + cmd = f"patch -p1 -i {patchfile}" + if silent: + cmd += ' > /dev/null' + else: + print(f"\n*** -> {cmd}") + sys.stdout.flush() + run(cmd) + + +__all__ = [ + 'get_moz_target', + 'list_patches', + 'patch', + 'run', + 'script_exit', + 'temp_cd', + 'get_options', +] + + +if __name__ == '__main__': + print('This is a module, not meant to be called directly.') + sys.exit(1) diff --git a/scripts/developer.py b/scripts/developer.py index fbd55a8..ac29a02 100644 --- a/scripts/developer.py +++ b/scripts/developer.py @@ -4,44 +4,28 @@ GUI for managing Camoufox patches. """ -import contextlib import os import re import easygui -from patch import list_patches, patch, run +from _mixin import find_src_dir, list_patches, patch, run, temp_cd def into_camoufox_dir(): - """Find and cd to the camoufox-* folder (this is located ..)""" - os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - folders = os.listdir('.') - for folder in folders: - if os.path.isdir(folder) and folder.startswith('camoufox-'): - os.chdir(folder) - break - else: - raise FileNotFoundError('No camoufox-* folder found') - - -@contextlib.contextmanager -def temp_cd(path): - # Temporarily change to a different working directory - _old_cwd = os.getcwd() - os.chdir(os.path.abspath(path)) - - try: - yield - finally: - os.chdir(_old_cwd) - + """Cd to the camoufox-* folder""" + this_script = os.path.dirname(os.path.abspath(__file__)) + # Go one directory up from the current script path + os.chdir(os.path.dirname(this_script)) + os.chdir(find_src_dir('.')) def reset_camoufox(): + """Reset the Camoufox source""" with temp_cd('..'): run('make clean') def run_patches(reverse=False): + """Apply patches""" patch_files = list_patches() if reverse: title = "Unpatch files" @@ -141,12 +125,13 @@ def check_patch(patch_file): def is_broken(patch_file): + """Check if a patch file is broken""" _, _, is_broken = check_patch(patch_file) return is_broken def get_rejects(patch_file): - # Returns a broken patch's rejects + """Get rejects from a patch file""" cmd = f'patch -p1 -i "{patch_file}" | tee /dev/stderr | sed -n -E \'s/^.*saving rejects to file (.*\\.rej)$/\\1/p\'' result = os.popen(cmd).read().strip() return result.split('\n') if result else [] @@ -173,6 +158,7 @@ GUI Choicebox def handle_choice(choice): + """Handle UI choice""" match choice: case "Reset workspace": reset_camoufox() diff --git a/scripts/package.py b/scripts/package.py index 1c65127..0047c77 100644 --- a/scripts/package.py +++ b/scripts/package.py @@ -3,23 +3,23 @@ import argparse import glob import os +import shlex import shutil -import subprocess +import sys import tempfile +from _mixin import find_src_dir, get_moz_target, run, temp_cd + UNNEEDED_PATHS = {'uninstall', 'pingsender.exe', 'pingsender', 'vaapitest', 'glxtest'} def run_command(command): - result = subprocess.run(command, capture_output=True, text=True) - if result.returncode != 0: - print(f"Error executing command: {' '.join(command)}") - print(f"Error output: {result.stderr}") - exit(1) - return result.stdout + """Execute a command with subprocess""" + cmd = ' '.join(shlex.quote(arg) for arg in command) + run(cmd) -def add_includes_to_package(package_file, includes, fonts, new_file, os): +def add_includes_to_package(package_file, includes, fonts, new_file, target): with tempfile.TemporaryDirectory() as temp_dir: # Extract package run_command(['7z', 'x', package_file, f'-o{temp_dir}']) @@ -33,10 +33,10 @@ def add_includes_to_package(package_file, includes, fonts, new_file, os): includes=includes, fonts=fonts, new_file=new_file, - os=os, + target=target, ) - if os == 'macos': + if target == 'macos': # Move Nightly/Nightly.app -> Camoufox.app nightly_dir = os.path.join(temp_dir, 'Nightly') shutil.move( @@ -56,7 +56,7 @@ def add_includes_to_package(package_file, includes, fonts, new_file, os): os.rmdir(camoufox_dir) # Create target_dir - if os == 'macos': + if target == 'macos': target_dir = os.path.join(temp_dir, 'Camoufox.app', 'Contents', 'Resources') else: target_dir = temp_dir @@ -84,7 +84,7 @@ def add_includes_to_package(package_file, includes, fonts, new_file, os): # Add launcher from launcher/dist/launch to temp_dir shutil.copy2( os.path.join('launcher', 'dist', 'launch'), - os.path.join(temp_dir, 'launch' + ('.exe' if os == 'windows' else '')), + os.path.join(temp_dir, 'launch' + ('.exe' if target == 'windows' else '')), ) # Remove unneeded paths @@ -98,7 +98,8 @@ def add_includes_to_package(package_file, includes, fonts, new_file, os): run_command(['7z', 'u', new_file, f'{temp_dir}/*', '-r', '-mx=9']) -def main(): +def get_args(): + """Get CLI parameters""" parser = argparse.ArgumentParser( description='Package Camoufox for different operating systems.' ) @@ -109,20 +110,47 @@ def main(): parser.add_argument('--version', required=True, help='Camoufox version') parser.add_argument('--release', required=True, help='Camoufox release number') parser.add_argument( - '--arch', choices=['x64', 'x86', 'arm64'], help='Architecture for Windows build' + '--arch', choices=['x86_64', 'i686', 'arm64'], help='Architecture for Windows build' ) + parser.add_argument('--no-locales', action='store_true', help='Do not package locales') parser.add_argument('--fonts', nargs='+', help='Font directories to include under fonts/') - args = parser.parse_args() + return parser.parse_args() + + +def main(): + """The main packaging function""" + args = get_args() # Determine file extension based on OS file_extensions = {'linux': 'tar.bz2', 'macos': 'dmg', 'windows': 'zip'} file_ext = file_extensions[args.os] - # Remove xpt_artifacts file if it exists - xpt_artifacts_pattern = f'camoufox-{args.version}-{args.release}.*.xpt_artifacts.*' - for xpt_file in glob.glob(xpt_artifacts_pattern): - if os.path.exists(xpt_file): - os.remove(xpt_file) + # Build the package + src_dir = find_src_dir('.', args.version, args.release) + moz_target = get_moz_target(target=args.os, arch=args.arch) + with temp_cd(src_dir): + # Create package files + if args.no_locales: + run('./mach package') + else: + run('cat browser/locales/shipped-locales | xargs ./mach package-multi-locale --locales') + # Find package files + search_path = os.path.abspath( + f'obj-{moz_target}/dist/camoufox-{args.version}-{args.release}.*.{file_ext}' + ) + + # Copy package files + for file in glob.glob(search_path): + if 'xpt_artifacts' in file: + print(f'Skipping xpt artifacts: {file}') + continue + print(f'Found package: {file}') + # Copy to root + shutil.copy2(file, '.') + break + else: + print(f"Error: No package file found matching pattern: {search_path}") + sys.exit(1) # Find the package file package_pattern = f'camoufox-{args.version}-{args.release}.en-US.*.{file_ext}' @@ -139,7 +167,7 @@ def main(): includes=args.includes, fonts=args.fonts, new_file=new_name, - os=args.os, + target=args.os, ) print(f"Packaging complete for {args.os}") diff --git a/scripts/patch.py b/scripts/patch.py index 0e8156d..4fcdd07 100644 --- a/scripts/patch.py +++ b/scripts/patch.py @@ -9,151 +9,103 @@ Run: python3 scripts/init-patch.py """ -import fnmatch import hashlib -import optparse import os import shutil import sys -import time +from dataclasses import dataclass -start_time = time.time() -parser = optparse.OptionParser() -parser.add_option('-n', '--no-execute', dest='no_execute', default=False, action="store_true") -parser.add_option( - '-P', '--no-settings-pane', dest='settings_pane', default=True, action="store_false" +from _mixin import ( + find_src_dir, + get_moz_target, + get_options, + list_patches, + patch, + run, + temp_cd, ) -options, args = parser.parse_args() + +options, args = get_options() """ -Helper functions +Main patcher functions """ -def script_exit(statuscode): - if (time.time() - start_time) > 60: - # print elapsed time - elapsed = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time)) - print(f"\n\aElapsed time: {elapsed}") - sys.stdout.flush() +@dataclass +class Patcher: + """Patch and prepare the Camoufox source""" - sys.exit(statuscode) + moz_target: str + target: str - -def run(cmd, exit_on_fail=True, do_print=True): - if not cmd: - return - if do_print: - print(cmd) - sys.stdout.flush() - if options.no_execute: - return None - retval = os.system(cmd) - if retval != 0 and exit_on_fail: - print(f"fatal error: command '{cmd}' failed") - sys.stdout.flush() - script_exit(1) - return retval - - -def patch(patchfile, reverse=False, silent=False): - if reverse: - cmd = f"patch -p1 -R -i {patchfile}" - else: - cmd = f"patch -p1 -i {patchfile}" - if silent: - cmd += ' > /dev/null' - else: - print(f"\n*** -> {cmd}") - sys.stdout.flush() - if options.no_execute: - return - retval = os.system(cmd) - if retval != 0: - print(f"fatal error: patch '{patchfile}' failed") - sys.stdout.flush() - script_exit(1) - - -def enter_srcdir(_dir=None): - if _dir is None: + def camoufox_patches(self): + """ + Apply all patches + """ version, release = extract_args() - dir = f"camoufox-{version}-{release}" - else: - dir = _dir - print(f"cd {dir}") - sys.stdout.flush() - if options.no_execute: - return - try: - os.chdir(dir) - except: - print(f"fatal error: can't change to '{dir}' folder.") - sys.stdout.flush() - script_exit(1) + with temp_cd(find_src_dir('.', version, release)): + # Create the base mozconfig file + run('cp -v ../assets/base.mozconfig mozconfig') + # Set cross building target + print(f'Using target: {self.moz_target}') + self._update_mozconfig() + if not options.mozconfig_only: + # Apply all other patches + for patch_file in list_patches(): + patch(patch_file) -def leave_srcdir(): - print("cd ..") - sys.stdout.flush() - if not options.no_execute: - os.chdir("..") + print('Complete!') + def _update_mozconfig(self): + """ + Helper for adding additional mozconfig code from assets/.mozconfig + """ + mozconfig_backup = "mozconfig.backup" + mozconfig = "mozconfig" + mozconfig_hash = "mozconfig.hash" -def list_files(root_dir, suffix): - for root, _, files in os.walk(root_dir): - for file in fnmatch.filter(files, suffix): - full_path = os.path.join(root, file) - relative_path = os.path.relpath(full_path, root_dir) - yield os.path.join('..', 'patches', relative_path).replace('\\', '/') + # Create backup if it doesn't exist + if not os.path.exists(mozconfig_backup): + if os.path.exists(mozconfig): + shutil.copy2(mozconfig, mozconfig_backup) + else: + with open(mozconfig_backup, 'w', encoding='utf-8') as f: + pass + # Read backup content + with open(mozconfig_backup, 'r', encoding='utf-8') as f: + content = f.read() -def list_patches(root_dir='../patches', suffix='*.patch'): - return sorted(list_files(root_dir, suffix), key=lambda f: os.path.basename(f)) + # Add target option + content += f"\nac_add_options --target={self.moz_target}\n" + + # Add target-specific mozconfig if it exists + target_mozconfig = os.path.join("..", "assets", f"{self.target}.mozconfig") + if os.path.exists(target_mozconfig): + with open(target_mozconfig, 'r', encoding='utf-8') as f: + content += f.read() + + # Calculate new hash + new_hash = hashlib.sha256(content.encode()).hexdigest() + + # Update mozconfig + print(f"-> Updating mozconfig, target is {self.moz_target}") + with open(mozconfig, 'w', encoding='utf-8') as f: + f.write(content) + with open(mozconfig_hash, 'w', encoding='utf-8') as f: + f.write(new_hash) def add_rustup(*targets): + """Add rust targets""" for rust_target in targets: - os.system(f'~/.cargo/bin/rustup target add "{rust_target}"') - - -""" -Main patcher function -""" - - -def camoufox_patches(): - enter_srcdir() - - # Create the right mozconfig file - run('cp -v ../assets/base.mozconfig mozconfig') - # Set cross building - print(f'Using target: {moz_target}') - _update_mozconfig() - - # Then apply all other patches - for patch_file in list_patches(): - patch(patch_file) - - leave_srcdir() - - -""" -Helpers for adding additional mozconfig code from assets/.mozconfig -""" - - -def _get_moz_target(): - if target == "linux": - return "aarch64-unknown-linux-gnu" if arch == "arm64" else f"{arch}-pc-linux-gnu" - if target == "windows": - return f"{arch}-pc-mingw32" - if target == "macos": - return "aarch64-apple-darwin" if arch == "arm64" else f"{arch}-apple-darwin" - raise ValueError(f"Unsupported target: {target}") + run(f'~/.cargo/bin/rustup target add "{rust_target}"') def _update_rustup(target): + """Add rust targets for the given target""" if target == "linux": add_rustup("aarch64-unknown-linux-gnu", "i686-unknown-linux-gnu") elif target == "windows": @@ -162,71 +114,25 @@ def _update_rustup(target): add_rustup("x86_64-apple-darwin", "aarch64-apple-darwin") -def _update_mozconfig(): - mozconfig_backup = "mozconfig.backup" - mozconfig = "mozconfig" - mozconfig_hash = "mozconfig.hash" - - # Create backup if it doesn't exist - if not os.path.exists(mozconfig_backup): - if os.path.exists(mozconfig): - shutil.copy2(mozconfig, mozconfig_backup) - else: - with open(mozconfig_backup, 'w') as f: - pass - - # Read backup content - with open(mozconfig_backup, 'r') as f: - content = f.read() - - # Add target option - content += f"\nac_add_options --target={moz_target}\n" - - # Add target-specific mozconfig if it exists - target_mozconfig = os.path.join("..", "assets", f"{target}.mozconfig") - if os.path.exists(target_mozconfig): - with open(target_mozconfig, 'r') as f: - content += f.read() - - # Calculate new hash - new_hash = hashlib.sha256(content.encode()).hexdigest() - - # Read old hash - old_hash = '' - if os.path.exists(mozconfig_hash): - with open(mozconfig_hash, 'r') as f: - old_hash = f.read().strip() - - # Update mozconfig if hash changed - if new_hash != old_hash: - print(f"-> Updating mozconfig, target is {moz_target}") - with open(mozconfig, 'w') as f: - f.write(content) - with open(mozconfig_hash, 'w') as f: - f.write(new_hash) - return True - return False - - """ Preparation """ def extract_args(): + """Get version and release from args""" if len(args) != 2: sys.stderr.write('error: please specify version and release of camoufox source') sys.exit(1) return args[0], args[1] -if __name__ == "__main__": - # Extract args - version, release = extract_args() +AVAILABLE_TARGETS = ["linux", "windows", "macos"] +AVAILABLE_ARCHS = ["x86_64", "arm64", "i686"] - # Get moz_target if passed to BUILD_TARGET environment variable - AVAILABLE_TARGETS = ["linux", "windows", "macos"] - AVAILABLE_ARCHS = ["x86_64", "arm64", "i686"] + +def extract_build_target(): + """Get moz_target if passed to BUILD_TARGET environment variable""" if os.environ.get('BUILD_TARGET'): target, arch = os.environ['BUILD_TARGET'].split(',') @@ -234,15 +140,28 @@ if __name__ == "__main__": assert arch in AVAILABLE_ARCHS, f"Unsupported architecture: {arch}" else: target, arch = "linux", "x86_64" - moz_target = _get_moz_target() - _update_rustup(target=target) + return target, arch + + +""" +Launcher +""" + +if __name__ == "__main__": + # Extract args + VERSION, RELEASE = extract_args() + + TARGET, ARCH = extract_build_target() + MOZ_TARGET = get_moz_target(TARGET, ARCH) + _update_rustup(TARGET) # Check if the folder exists - if not os.path.exists(f'camoufox-{version}-{release}/configure.py'): + if not os.path.exists(f'camoufox-{VERSION}-{RELEASE}/configure.py'): sys.stderr.write('error: folder doesn\'t look like a Firefox folder.') sys.exit(1) # Apply the patches - camoufox_patches() + patcher = Patcher(MOZ_TARGET, TARGET) + patcher.camoufox_patches() sys.exit(0) # ensure 0 exit code