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
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 \

View file

@ -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
```
<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
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
```
</details>

View file

@ -2,7 +2,7 @@
if [ $# -ne 2 ]; then
echo "Usage: $0 <arch> <os>"
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

View file

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

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.
"""
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()

View file

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

View file

@ -9,151 +9,103 @@ Run:
python3 scripts/init-patch.py <version> <release>
"""
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/<target>.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/<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}")
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