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
This commit is contained in:
daijro 2024-08-01 20:33:46 -05:00
parent 717aa9db36
commit bf245006b2
8 changed files with 410 additions and 282 deletions

View file

@ -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 rpms := python3 python3-devel p7zip golang msitools wget aria2c
pacman := python python-pip p7zip go 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: help:
@echo "Available targets:" @echo "Available targets:"
@ -23,6 +23,7 @@ help:
@echo " clean - Remove build artifacts" @echo " clean - Remove build artifacts"
@echo " distclean - Remove everything including downloads" @echo " distclean - Remove everything including downloads"
@echo " build - Build Camoufox" @echo " build - Build Camoufox"
@echo " set-target - Change the build target with BUILD_TARGET"
@echo " package-linux - Package Camoufox for Linux" @echo " package-linux - Package Camoufox for Linux"
@echo " package-macos - Package Camoufox for macOS" @echo " package-macos - Package Camoufox for macOS"
@echo " package-windows - Package Camoufox for Windows" @echo " package-windows - Package Camoufox for Windows"
@ -63,6 +64,9 @@ dir:
python3 scripts/patch.py $(version) $(release) python3 scripts/patch.py $(version) $(release)
touch $(cf_source_dir)/_READY touch $(cf_source_dir)/_READY
set-target:
python3 scripts/patch.py $(version) $(release) --mozconfig-only
mozbootstrap: mozbootstrap:
cd $(cf_source_dir) && MOZBUILD_STATE_PATH=$$HOME/.mozbuild ./mach --no-interactive bootstrap --application-choice=browser 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 python ./scripts/developer.py
check-arch: check-arch:
@if [ "$(arch)" != "x64" ] && [ "$(arch)" != "x86" ] && [ "$(arch)" != "arm64" ]; then \ @if ! echo "x86_64 i686 arm64" | grep -qw "$(arch)"; then \
echo "Error: Invalid arch value. Must be x64, x86, or arm64."; \ echo "Error: Invalid arch value. Must be x86_64, i686, or arm64."; \
exit 1; \ exit 1; \
fi fi
build-launcher: check-arch build-launcher: check-arch
cd launcher && ./build.sh $(arch) $(os) cd launcher && bash build.sh $(arch) $(os)
package-common: check-arch package-linux:
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
make build-launcher arch=$(arch) os=linux; make build-launcher arch=$(arch) os=linux;
python3 scripts/package.py linux \ python3 scripts/package.py linux \
--includes \ --includes \
@ -116,7 +116,7 @@ package-linux: package-common
--arch $(arch) \ --arch $(arch) \
--fonts windows macos linux --fonts windows macos linux
package-macos: package-common package-macos:
make build-launcher arch=$(arch) os=macos; make build-launcher arch=$(arch) os=macos;
python3 scripts/package.py macos \ python3 scripts/package.py macos \
--includes \ --includes \
@ -126,7 +126,7 @@ package-macos: package-common
--arch $(arch) \ --arch $(arch) \
--fonts windows linux --fonts windows linux
package-windows: package-common package-windows:
make build-launcher arch=$(arch) os=windows; make build-launcher arch=$(arch) os=windows;
python3 scripts/package.py windows \ python3 scripts/package.py windows \
--includes \ --includes \

View file

@ -217,7 +217,7 @@ Miscellaneous (WebGl spoofing, battery status, etc)
#### Playwright support #### 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 - Various config patches to evade bot detection
#### Debloat/Optimizations #### Debloat/Optimizations
@ -322,16 +322,33 @@ After that, you have to bootstrap your system to be able to build Camoufox. You
make bootstrap 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 ```bash
make build python3 multibuild.py --target linux windows macos --arch x86_64 arm64 i686
# Example package commands:
make package-linux arch=x64
make package-windows arch=x86
make package-macos arch=amd64
``` ```
<details>
<summary>
CLI Parameters
</summary>
```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
```
</details>
### Using Docker ### Using Docker
Camoufox can be built through Docker on all platforms. Camoufox can be built through Docker on all platforms.
@ -374,10 +391,15 @@ Docker CLI Parameters
```bash ```bash
Options: Options:
-h, --help show this help message and exit -h, --help show this help message and exit
--target {linux,windows,macos} --target {linux,windows,macos} [{linux,windows,macos} ...]
Target platform for the build Target platforms to build
--arch {x86_64,arm64,i686} --arch {x86_64,arm64,i686} [{x86_64,arm64,i686} ...]
Target architecture for the build 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
``` ```
</details> </details>

View file

@ -2,7 +2,7 @@
if [ $# -ne 2 ]; then if [ $# -ne 2 ]; then
echo "Usage: $0 <arch> <os>" echo "Usage: $0 <arch> <os>"
echo "arch: x64, x86, arm64" echo "arch: x86_64, i686, arm64"
echo "os: linux, windows, macos" echo "os: linux, windows, macos"
exit 1 exit 1
fi fi
@ -11,8 +11,8 @@ ARCH=$1
OS=$2 OS=$2
case $ARCH in case $ARCH in
x64) GOARCH=amd64 ;; x86_64) GOARCH=amd64 ;;
x86) GOARCH=386 ;; i686) GOARCH=386 ;;
arm64) GOARCH=arm64 ;; arm64) GOARCH=arm64 ;;
*) echo "Invalid architecture"; exit 1 ;; *) echo "Invalid architecture"; exit 1 ;;
esac esac

View file

@ -1,5 +1,18 @@
""" """
Easy build CLI for Camoufox 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. 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"] 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') print(f'\n------------\n{cmd}\n------------\n')
retval = os.system(cmd) retval = os.system(cmd)
if retval != 0 and exit_on_fail: if retval != 0 and exit_on_fail:
print("fatal error: command '{}' failed".format(cmd)) print(f"fatal error: command '{cmd}' failed")
sys.exit(1) sys.exit(1)
return retval return retval
@ -29,51 +42,43 @@ class BSYS:
target: str target: str
arch: str arch: str
def bootstrap(self): @staticmethod
exec('make bootstrap') def bootstrap():
"""Bootstrap the build system"""
run('make bootstrap')
def build(self): def build(self):
"""Build the Camoufox source code"""
os.environ['BUILD_TARGET'] = f'{self.target},{self.arch}' os.environ['BUILD_TARGET'] = f'{self.target},{self.arch}'
exec(f'make build') run('make build')
def package(self): def package(self):
if self.arch == 'x86_64': """Package the Camoufox source code"""
_arch = 'x64' run(f'make package-{self.target} arch={self.arch}')
else:
_arch = 'arm64' def update_target(self):
exec(f'make package-{self.target} arch={_arch}') """Change the build target"""
os.environ['BUILD_TARGET'] = f'{self.target},{self.arch}'
run('make set-target')
@property @property
def assets(self) -> List[str]: 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) return glob.glob(package_pattern)
def clean(self): @staticmethod
exec('make clean') def clean():
"""Clean the Camoufox directory"""
run('make clean')
def main(): def run_build(target, arch):
parser = argparse.ArgumentParser(description="Easy build CLI for Camoufox") """
parser.add_argument( Run the build for the given target and architecture
"--target", choices=AVAILABLE_TARGETS, required=True, help="Target platform for the build" """
) builder = BSYS(target, arch)
parser.add_argument( builder.update_target()
"--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()
# Run build # Run build
builder.build() builder.build()
# Run package # Run package
@ -83,5 +88,40 @@ def main():
os.rename(asset, f'dist/{asset}') 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__": if __name__ == "__main__":
main() main()

133
scripts/_mixin.py Normal file
View file

@ -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)

View file

@ -4,44 +4,28 @@
GUI for managing Camoufox patches. GUI for managing Camoufox patches.
""" """
import contextlib
import os import os
import re import re
import easygui 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(): def into_camoufox_dir():
"""Find and cd to the camoufox-* folder (this is located ..)""" """Cd to the camoufox-* folder"""
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) this_script = os.path.dirname(os.path.abspath(__file__))
folders = os.listdir('.') # Go one directory up from the current script path
for folder in folders: os.chdir(os.path.dirname(this_script))
if os.path.isdir(folder) and folder.startswith('camoufox-'): os.chdir(find_src_dir('.'))
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)
def reset_camoufox(): def reset_camoufox():
"""Reset the Camoufox source"""
with temp_cd('..'): with temp_cd('..'):
run('make clean') run('make clean')
def run_patches(reverse=False): def run_patches(reverse=False):
"""Apply patches"""
patch_files = list_patches() patch_files = list_patches()
if reverse: if reverse:
title = "Unpatch files" title = "Unpatch files"
@ -141,12 +125,13 @@ def check_patch(patch_file):
def is_broken(patch_file): def is_broken(patch_file):
"""Check if a patch file is broken"""
_, _, is_broken = check_patch(patch_file) _, _, is_broken = check_patch(patch_file)
return is_broken return is_broken
def get_rejects(patch_file): 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\'' 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() result = os.popen(cmd).read().strip()
return result.split('\n') if result else [] return result.split('\n') if result else []
@ -173,6 +158,7 @@ GUI Choicebox
def handle_choice(choice): def handle_choice(choice):
"""Handle UI choice"""
match choice: match choice:
case "Reset workspace": case "Reset workspace":
reset_camoufox() reset_camoufox()

View file

@ -3,23 +3,23 @@
import argparse import argparse
import glob import glob
import os import os
import shlex
import shutil import shutil
import subprocess import sys
import tempfile import tempfile
from _mixin import find_src_dir, get_moz_target, run, temp_cd
UNNEEDED_PATHS = {'uninstall', 'pingsender.exe', 'pingsender', 'vaapitest', 'glxtest'} UNNEEDED_PATHS = {'uninstall', 'pingsender.exe', 'pingsender', 'vaapitest', 'glxtest'}
def run_command(command): def run_command(command):
result = subprocess.run(command, capture_output=True, text=True) """Execute a command with subprocess"""
if result.returncode != 0: cmd = ' '.join(shlex.quote(arg) for arg in command)
print(f"Error executing command: {' '.join(command)}") run(cmd)
print(f"Error output: {result.stderr}")
exit(1)
return result.stdout
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: with tempfile.TemporaryDirectory() as temp_dir:
# Extract package # Extract package
run_command(['7z', 'x', package_file, f'-o{temp_dir}']) 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, includes=includes,
fonts=fonts, fonts=fonts,
new_file=new_file, new_file=new_file,
os=os, target=target,
) )
if os == 'macos': if target == 'macos':
# Move Nightly/Nightly.app -> Camoufox.app # Move Nightly/Nightly.app -> Camoufox.app
nightly_dir = os.path.join(temp_dir, 'Nightly') nightly_dir = os.path.join(temp_dir, 'Nightly')
shutil.move( shutil.move(
@ -56,7 +56,7 @@ def add_includes_to_package(package_file, includes, fonts, new_file, os):
os.rmdir(camoufox_dir) os.rmdir(camoufox_dir)
# Create target_dir # Create target_dir
if os == 'macos': if target == 'macos':
target_dir = os.path.join(temp_dir, 'Camoufox.app', 'Contents', 'Resources') target_dir = os.path.join(temp_dir, 'Camoufox.app', 'Contents', 'Resources')
else: else:
target_dir = temp_dir 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 # Add launcher from launcher/dist/launch to temp_dir
shutil.copy2( shutil.copy2(
os.path.join('launcher', 'dist', 'launch'), 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 # 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']) run_command(['7z', 'u', new_file, f'{temp_dir}/*', '-r', '-mx=9'])
def main(): def get_args():
"""Get CLI parameters"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Package Camoufox for different operating systems.' 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('--version', required=True, help='Camoufox version')
parser.add_argument('--release', required=True, help='Camoufox release number') parser.add_argument('--release', required=True, help='Camoufox release number')
parser.add_argument( 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/') 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 # Determine file extension based on OS
file_extensions = {'linux': 'tar.bz2', 'macos': 'dmg', 'windows': 'zip'} file_extensions = {'linux': 'tar.bz2', 'macos': 'dmg', 'windows': 'zip'}
file_ext = file_extensions[args.os] file_ext = file_extensions[args.os]
# Remove xpt_artifacts file if it exists # Build the package
xpt_artifacts_pattern = f'camoufox-{args.version}-{args.release}.*.xpt_artifacts.*' src_dir = find_src_dir('.', args.version, args.release)
for xpt_file in glob.glob(xpt_artifacts_pattern): moz_target = get_moz_target(target=args.os, arch=args.arch)
if os.path.exists(xpt_file): with temp_cd(src_dir):
os.remove(xpt_file) # 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 # Find the package file
package_pattern = f'camoufox-{args.version}-{args.release}.en-US.*.{file_ext}' package_pattern = f'camoufox-{args.version}-{args.release}.en-US.*.{file_ext}'
@ -139,7 +167,7 @@ def main():
includes=args.includes, includes=args.includes,
fonts=args.fonts, fonts=args.fonts,
new_file=new_name, new_file=new_name,
os=args.os, target=args.os,
) )
print(f"Packaging complete for {args.os}") print(f"Packaging complete for {args.os}")

View file

@ -9,160 +9,59 @@ Run:
python3 scripts/init-patch.py <version> <release> python3 scripts/init-patch.py <version> <release>
""" """
import fnmatch
import hashlib import hashlib
import optparse
import os import os
import shutil import shutil
import sys import sys
import time from dataclasses import dataclass
start_time = time.time() from _mixin import (
parser = optparse.OptionParser() find_src_dir,
parser.add_option('-n', '--no-execute', dest='no_execute', default=False, action="store_true") get_moz_target,
parser.add_option( get_options,
'-P', '--no-settings-pane', dest='settings_pane', default=True, action="store_false" list_patches,
patch,
run,
temp_cd,
) )
options, args = parser.parse_args()
options, args = get_options()
""" """
Helper functions Main patcher functions
""" """
def script_exit(statuscode): @dataclass
if (time.time() - start_time) > 60: class Patcher:
# print elapsed time """Patch and prepare the Camoufox source"""
elapsed = time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time))
print(f"\n\aElapsed time: {elapsed}")
sys.stdout.flush()
sys.exit(statuscode) moz_target: str
target: str
def camoufox_patches(self):
def run(cmd, exit_on_fail=True, do_print=True): """
if not cmd: Apply all patches
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:
version, release = extract_args() version, release = extract_args()
dir = f"camoufox-{version}-{release}" with temp_cd(find_src_dir('.', version, release)):
else: # Create the base mozconfig file
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)
def leave_srcdir():
print("cd ..")
sys.stdout.flush()
if not options.no_execute:
os.chdir("..")
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('\\', '/')
def list_patches(root_dir='../patches', suffix='*.patch'):
return sorted(list_files(root_dir, suffix), key=lambda f: os.path.basename(f))
def add_rustup(*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') run('cp -v ../assets/base.mozconfig mozconfig')
# Set cross building # Set cross building target
print(f'Using target: {moz_target}') print(f'Using target: {self.moz_target}')
_update_mozconfig() self._update_mozconfig()
# Then apply all other patches if not options.mozconfig_only:
# Apply all other patches
for patch_file in list_patches(): for patch_file in list_patches():
patch(patch_file) patch(patch_file)
leave_srcdir() print('Complete!')
def _update_mozconfig(self):
""" """
Helpers for adding additional mozconfig code from assets/<target>.mozconfig Helper for adding additional mozconfig code from assets/<target>.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}")
def _update_rustup(target):
if target == "linux":
add_rustup("aarch64-unknown-linux-gnu", "i686-unknown-linux-gnu")
elif target == "windows":
add_rustup("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc", "i686-pc-windows-msvc")
elif target == "macos":
add_rustup("x86_64-apple-darwin", "aarch64-apple-darwin")
def _update_mozconfig():
mozconfig_backup = "mozconfig.backup" mozconfig_backup = "mozconfig.backup"
mozconfig = "mozconfig" mozconfig = "mozconfig"
mozconfig_hash = "mozconfig.hash" mozconfig_hash = "mozconfig.hash"
@ -172,40 +71,47 @@ def _update_mozconfig():
if os.path.exists(mozconfig): if os.path.exists(mozconfig):
shutil.copy2(mozconfig, mozconfig_backup) shutil.copy2(mozconfig, mozconfig_backup)
else: else:
with open(mozconfig_backup, 'w') as f: with open(mozconfig_backup, 'w', encoding='utf-8') as f:
pass pass
# Read backup content # Read backup content
with open(mozconfig_backup, 'r') as f: with open(mozconfig_backup, 'r', encoding='utf-8') as f:
content = f.read() content = f.read()
# Add target option # Add target option
content += f"\nac_add_options --target={moz_target}\n" content += f"\nac_add_options --target={self.moz_target}\n"
# Add target-specific mozconfig if it exists # Add target-specific mozconfig if it exists
target_mozconfig = os.path.join("..", "assets", f"{target}.mozconfig") target_mozconfig = os.path.join("..", "assets", f"{self.target}.mozconfig")
if os.path.exists(target_mozconfig): if os.path.exists(target_mozconfig):
with open(target_mozconfig, 'r') as f: with open(target_mozconfig, 'r', encoding='utf-8') as f:
content += f.read() content += f.read()
# Calculate new hash # Calculate new hash
new_hash = hashlib.sha256(content.encode()).hexdigest() new_hash = hashlib.sha256(content.encode()).hexdigest()
# Read old hash # Update mozconfig
old_hash = '' print(f"-> Updating mozconfig, target is {self.moz_target}")
if os.path.exists(mozconfig_hash): with open(mozconfig, 'w', encoding='utf-8') as f:
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) f.write(content)
with open(mozconfig_hash, 'w') as f: with open(mozconfig_hash, 'w', encoding='utf-8') as f:
f.write(new_hash) f.write(new_hash)
return True
return False
def add_rustup(*targets):
"""Add rust targets"""
for rust_target in targets:
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":
add_rustup("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc", "i686-pc-windows-msvc")
elif target == "macos":
add_rustup("x86_64-apple-darwin", "aarch64-apple-darwin")
""" """
@ -214,35 +120,48 @@ Preparation
def extract_args(): def extract_args():
"""Get version and release from args"""
if len(args) != 2: if len(args) != 2:
sys.stderr.write('error: please specify version and release of camoufox source') sys.stderr.write('error: please specify version and release of camoufox source')
sys.exit(1) sys.exit(1)
return args[0], args[1] return args[0], args[1]
if __name__ == "__main__":
# Extract args
version, release = extract_args()
# Get moz_target if passed to BUILD_TARGET environment variable
AVAILABLE_TARGETS = ["linux", "windows", "macos"] AVAILABLE_TARGETS = ["linux", "windows", "macos"]
AVAILABLE_ARCHS = ["x86_64", "arm64", "i686"] 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'): if os.environ.get('BUILD_TARGET'):
target, arch = os.environ['BUILD_TARGET'].split(',') target, arch = os.environ['BUILD_TARGET'].split(',')
assert target in AVAILABLE_TARGETS, f"Unsupported target: {target}" assert target in AVAILABLE_TARGETS, f"Unsupported target: {target}"
assert arch in AVAILABLE_ARCHS, f"Unsupported architecture: {arch}" assert arch in AVAILABLE_ARCHS, f"Unsupported architecture: {arch}"
else: else:
target, arch = "linux", "x86_64" target, arch = "linux", "x86_64"
moz_target = _get_moz_target() return target, arch
_update_rustup(target=target)
"""
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 # 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.stderr.write('error: folder doesn\'t look like a Firefox folder.')
sys.exit(1) sys.exit(1)
# Apply the patches # Apply the patches
camoufox_patches() patcher = Patcher(MOZ_TARGET, TARGET)
patcher.camoufox_patches()
sys.exit(0) # ensure 0 exit code sys.exit(0) # ensure 0 exit code