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:
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",
],
)