tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8aeb5a01a7a9d0ac66168e24386e456030d0c79c
parent fa51be30895198ec9ee19a43dbca622d111e14a5
Author: Beatriz Rizental <bea@torproject.org>
Date:   Tue, 19 Aug 2025 15:58:40 +0200

TB 43564: Modify ./mach bootstrap for Tor Browser

Diffstat:
Mbuild/moz.configure/bootstrap.configure | 4+++-
Abuild/moz.configure/torbrowser-resources.configure | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/gradle.configure | 7+++++--
Mmoz.configure | 1+
Mpython/mozboot/mozboot/bootstrap.py | 14+++++++-------
Mpython/mozbuild/mozbuild/backend/base.py | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpython/mozbuild/mozbuild/tbbutils.py | 17+++++++++++++++--
Mpython/mozbuild/mozbuild/test/test_tbbutils.py | 17+++++++++++++++--
8 files changed, 270 insertions(+), 14 deletions(-)

diff --git a/build/moz.configure/bootstrap.configure b/build/moz.configure/bootstrap.configure @@ -227,7 +227,9 @@ def bootstrap_path(path, **kwargs): log.info("no path found in tbb/out for %s", artifact) return False - artifact_index = mozbuild.tbbutils.get_artifact_index(artifact_path) + artifact_index = mozbuild.tbbutils.get_artifact_index( + artifact_path, artifact + ) index_file = os.path.join(toolchains_base_dir, "indices", artifact) try: with open(index_file) as fh: diff --git a/build/moz.configure/torbrowser-resources.configure b/build/moz.configure/torbrowser-resources.configure @@ -0,0 +1,141 @@ +option( + "--with-tor-expert-bundle", + env="TOR_EXPERT_BUNDLE", + nargs=1, + default=None, + help="Path to location of tor-expert-bundle directory.", + when=is_desktop_build, +) + + +@depends( + "--with-tor-expert-bundle", + mozbuild_state_path, + bootstrap_path( + "tor-expert-bundle", + when=depends("--with-tor-expert-bundle", when=is_desktop_build)( + lambda x: not x + ), + ), + when=is_desktop_build, +) +@checking("for tor-expert-bundle") +@imports(_from="pathlib", _import="Path") +def tor_expert_bundle(value, mozbuild_state_path, _bootstrapped): + if value: + path = Path(value[0]) + # TODO: Do a more thorough check on the directory. + if path.is_dir(): + return value[0] + else: + die("--with-tor-expert-bundle must point to a real directory.") + + bootstrapped_location = Path(mozbuild_state_path) / "tor-expert-bundle" + if bootstrapped_location.is_dir(): + return str(bootstrapped_location) + + # tor-expert-bundle is not required for building. + return None + + +set_config("TOR_EXPERT_BUNDLE", tor_expert_bundle, when=is_desktop_build) + + +# Android +# ------------------------------------------------- + + +@depends(build_project) +def is_android_build(build_project): + return build_project == "mobile/android" + + +option( + "--with-tor-expert-bundle-aar", + env="TOR_EXPERT_BUNDLE_AAR", + nargs=1, + default=None, + help="Path to location of tor-expert-bundle.aar archive.", + when=is_android_build, +) + + +@depends( + "--with-tor-expert-bundle-aar", + mozbuild_state_path, + bootstrap_path( + "tor-expert-bundle-aar", + no_unpack=True, + when=depends("--with-tor-expert-bundle-aar", when=is_android_build)( + lambda x: not x + ), + ), + when=is_android_build, +) +@checking("for tor-expert-bundle.aar") +@imports(_from="pathlib", _import="Path") +def tor_expert_bundle_aar(value, mozbuild_state_path, _bootstrapped): + if value: + path = Path(value[0]) + if path.suffix.lower() == ".aar": + return value[0] + else: + die("--with-tor-expert-bundle-aar must point to a AAR archive.") + + bootstrapped_location = Path(mozbuild_state_path) / "tor-expert-bundle.aar" + if bootstrapped_location.is_file(): + return str(bootstrapped_location) + + die( + "tor-expert-bundle-aar not found. Either enable bootstrap, or provide a path with --with-tor-expert-bundle-aar." + ) + + +set_config("TOR_EXPERT_BUNDLE_AAR", tor_expert_bundle_aar) + + +option( + "--with-application-services", + env="APPLICATION_SERVICES", + nargs=1, + default=None, + help="Path to location of application-services gradle lib.", + when=is_android_build, +) + + +@depends( + "--with-application-services", + mozbuild_state_path, + bootstrap_path( + "application-services", + when=depends("--with-application-services", when=is_android_build)( + lambda x: not x + ), + ), + when=is_android_build, +) +@checking("for application-services") +@imports(_from="pathlib", _import="Path") +@imports("mozbuild.tbbutils") +def application_services(value, mozbuild_state_path, _bootstrapped): + as_location = None + + if value: + path = Path(value[0]) + if path.is_dir(): + as_location = path + else: + die("--with-application-services must point to a directory.") + else: + bootstrapped_location = Path(mozbuild_state_path) / "application-services/maven" + if bootstrapped_location.is_dir(): + as_location = bootstrapped_location + + if not as_location: + # application-services is required for building. + die( + "application-services not found. Either enable bootstrap, or provide a path with --with-application-services." + ) + + return as_location diff --git a/mobile/android/gradle.configure b/mobile/android/gradle.configure @@ -746,13 +746,16 @@ set_config( ) -@depends("GRADLE_MAVEN_REPOSITORIES") +@depends(application_services, "GRADLE_MAVEN_REPOSITORIES") @imports(_from="os.path", _import="isdir") -def gradle_maven_repositories(values): +def gradle_maven_repositories(app_services_location, values): if not values: die("GRADLE_MAVEN_REPOSITORIES must not be empty") if not all(values): die("GRADLE_MAVEN_REPOSITORIES entries must not be empty") + + # Prepend the location of the custom a-s location + values = (f"file://{app_services_location}",) + values return values diff --git a/moz.configure b/moz.configure @@ -226,6 +226,7 @@ def check_prog(*args, **kwargs): include("build/moz.configure/toolchain.configure", when="--enable-compile-environment") include("build/moz.configure/basebrowser-resources.configure") +include("build/moz.configure/torbrowser-resources.configure") include("build/moz.configure/pkg.configure") include("build/moz.configure/memory.configure", when="--enable-compile-environment") diff --git a/python/mozboot/mozboot/bootstrap.py b/python/mozboot/mozboot/bootstrap.py @@ -50,26 +50,26 @@ Artifact builds download prebuilt C++ components rather than building them locally. Artifact builds are faster! Artifact builds are recommended for people working on Tor Browser or -Base Browser for Android frontends, or the GeckoView Java API. They are unsuitable +Tor Browser for Android frontends, or the GeckoView Java API. They are unsuitable for those working on C++ code. For more information see: https://firefox-source-docs.mozilla.org/contributing/build/artifact_builds.html. -# Note to Base Browser developers +# Note to Tor Browser developers This is still highly experimental. Expect bugs! -Please choose the version of Base Browser you want to build (see note above): +Please choose the version of Tor Browser you want to build (see note above): %s Your choice: """ APPLICATIONS = OrderedDict([ - ("Base Browser for Desktop Artifact Mode", "browser_artifact_mode"), - ("Base Browser for Desktop", "browser"), + ("Tor Browser for Desktop Artifact Mode", "browser_artifact_mode"), + ("Tor Browser for Desktop", "browser"), ( - "GeckoView/Base Browser for Android Artifact Mode", + "GeckoView/Tor Browser for Android Artifact Mode", "mobile_android_artifact_mode", ), - ("GeckoView/Base Browser for Android", "mobile_android"), + ("GeckoView/Tor Browser for Android", "mobile_android"), ("SpiderMonkey JavaScript engine", "js"), ]) diff --git a/python/mozbuild/mozbuild/backend/base.py b/python/mozbuild/mozbuild/backend/base.py @@ -278,6 +278,27 @@ class BuildBackend(LoggingMixin): noscript_target_filename = "{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi" noscript_location = config.substs.get("NOSCRIPT") + if app == "mobile/android": + # Set up NoScript extension + # We put it in the srcdir... It will be moved to the APK in the gradle build. + if noscript_location: + noscript_target = ( + Path(config.topsrcdir) + / "mobile/android/fenix/app/src/main/assets/extensions" + / noscript_target_filename + ) + self.log( + logging.INFO, + "_setup_tor_browser_environment", + { + "noscript_location": noscript_location, + "noscript_target": str(noscript_target), + }, + "Creating symlink for NoScript from {noscript_location} to {noscript_target}", + ) + + _infallible_symlink(noscript_location, noscript_target) + if app == "browser": tbdir = Path(config.topobjdir) / "dist" / "bin" @@ -286,12 +307,16 @@ class BuildBackend(LoggingMixin): paths = { "docs": tbdir / "Contents/Resources/TorBrowser/Docs", "exts": tbdir / "Contents/Resources/distribution/extensions", + "tor_bin": tbdir / "Contents/MacOS/tor", + "tor_config": tbdir / "Contents/Resources/TorBrowser/Tor", "fonts": tbdir / "Resources/fonts", } else: paths = { "docs": tbdir / "TorBrowser/Docs", "exts": tbdir / "distribution/extensions", + "tor_bin": tbdir / "TorBrowser/Tor", + "tor_config": tbdir / "TorBrowser/Data/Tor", "fonts": tbdir / "fonts", } @@ -317,6 +342,64 @@ class BuildBackend(LoggingMixin): paths["exts"], ) + expert_bundle_location = config.substs.get("TOR_EXPERT_BUNDLE") + if expert_bundle_location: + expert_bundle_location = Path(expert_bundle_location) + if not expert_bundle_location.is_dir(): + return + + self.log( + logging.INFO, + "_setup_tor_browser_environment", + { + "expert_bundle_location": str(expert_bundle_location), + }, + "Setting up tor-expert-bundle resources from {expert_bundle_location}", + ) + + # Set up Tor configuration files + paths["tor_config"].mkdir(parents=True, exist_ok=True) + for file in ["geoip", "geoip6", "torrc-defaults"]: + target = paths["tor_config"] / file + _infallible_symlink(expert_bundle_location / "data" / file, target) + + # Set up Conjure documentation + conjust_docs_location = paths["docs"] / "conjure" + conjust_docs_location.mkdir(parents=True, exist_ok=True) + conjure_readme = conjust_docs_location / "README.CONJURE.md" + _infallible_symlink( + expert_bundle_location + / "tor/pluggable_transports/README.CONJURE.md", + conjure_readme, + ) + + # Set up pluggable transports + paths["tor_bin"].mkdir(parents=True, exist_ok=True) + pluggable_transports_location = ( + expert_bundle_location / "tor/pluggable_transports" + ) + pluggable_transports_target = paths["tor_bin"] / "PluggableTransports" + pluggable_transports_target.mkdir(parents=True, exist_ok=True) + for file in pluggable_transports_location.iterdir(): + # We only want the PT executables. + if os.access(file, os.X_OK) or file.suffix.lower() == ".exe": + target = pluggable_transports_target / file.name + _infallible_symlink(file, target) + + # Setup Tor binary + for item in Path(expert_bundle_location / "tor").iterdir(): + target = paths["tor_bin"] / item.name + + if item.is_file(): + _infallible_symlink(item, target) + + # Set up licenses + licenses_location = paths["docs"] / "Licenses" + licenses_location.mkdir(parents=True, exist_ok=True) + for item in (expert_bundle_location / "docs").iterdir(): + target = licenses_location / item.name + _infallible_symlink(item, target) + def post_build(self, config, output, jobs, verbose, status): """Called late during 'mach build' execution, after `build(...)` has finished. diff --git a/python/mozbuild/mozbuild/tbbutils.py b/python/mozbuild/mozbuild/tbbutils.py @@ -17,6 +17,11 @@ def list_files_http(url): if href == "../": continue + if "tor-expert-bundle-aar" in href: + href = f"{href.rstrip('/')}/tor-expert-bundle.aar" + elif "tor-expert-bundle" in href: + href = f"{href.rstrip('/')}/tor-expert-bundle.tar.gz" + links.append(href) return links @@ -26,6 +31,9 @@ TOR_BROWSER_BUILD_ARTIFACTS = [ # Tor Browser Build-only artifacts, these artifacts are not common with Firefox. "noscript", "fonts", + "tor-expert-bundle", + "tor-expert-bundle-aar", + "application-services", ] # Mapping of artifacts from taskcluster to tor-browser-build. @@ -41,14 +49,19 @@ ARTIFACT_NAME_MAP = { } -def get_artifact_index(artifact_path): +def get_artifact_index(artifact_path, artifact): """ Return a unique identifier for the given artifact based on its path. In most cases, artifacts built by tor-browser-build include part of their SHA sum or version in the filename, so the file name itself serves as a unique - identifier. + identifier. However, some artifacts are stored within subfolders where the file + name alone is not unique — in those cases, the name of the parent directory + provides the unique identifier instead. """ + if artifact in ["tor-expert-bundle"]: + return artifact_path.rsplit("/", 2)[-2] + return artifact_path.rsplit("/", 1)[-1] diff --git a/python/mozbuild/mozbuild/test/test_tbbutils.py b/python/mozbuild/mozbuild/test/test_tbbutils.py @@ -4,7 +4,11 @@ from unittest.mock import MagicMock, patch import mozunit -from mozbuild.tbbutils import get_artifact_index, get_artifact_path, list_files_http +from mozbuild.tbbutils import ( + get_artifact_index, + get_artifact_path, + list_files_http, +) class TestGetArtifactName(unittest.TestCase): @@ -36,9 +40,16 @@ class TestGetArtifactName(unittest.TestCase): class TestGetArtifactIndex(unittest.TestCase): def test_regular_artifact(self): + artifact = "tor" path = "https://tb-build-06.torproject.org/~tb-builder/tor-browser-build/out/tor/tor-b1f9824464dc-linux-x86_64-b0ffe2.tar.gz" expected = "tor-b1f9824464dc-linux-x86_64-b0ffe2.tar.gz" - self.assertEqual(get_artifact_index(path), expected) + self.assertEqual(get_artifact_index(path, artifact), expected) + + def test_expert_bundle_artifact(self): + artifact = "tor-expert-bundle" + path = "https://tb-build-06.torproject.org/~tb-builder/tor-browser-build/out/tor-expert-bundle/tor-expert-bundle-linux-x86_64-tbb-nightly.2025.10.14-d9aa09/" + expected = "tor-expert-bundle-linux-x86_64-tbb-nightly.2025.10.14-d9aa09" + self.assertEqual(get_artifact_index(path, artifact), expected) class TestGetArtifactPath(unittest.TestCase): @@ -145,6 +156,7 @@ class TestListFilesHttp(unittest.TestCase): def test_tor_expert_bundle_rewrites(self, mock_urlopen): html = """ <a href="tor-expert-bundle">bundle</a> + <a href="tor-expert-bundle-aar">bundle</a> """ mock_resp = MagicMock() mock_resp.status = 200 @@ -156,6 +168,7 @@ class TestListFilesHttp(unittest.TestCase): result, [ "tor-expert-bundle/tor-expert-bundle.tar.gz", + "tor-expert-bundle-aar/tor-expert-bundle.aar", ], )