From 371409a96215aabd2df1178271396206ac8caef3 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Thu, 23 Oct 2025 21:13:58 +0200
Subject: [PATCH 01/14] Added android debugging support
---
changes/2351.feature.md | 1 +
src/briefcase/integrations/android_sdk.py | 14 +-
src/briefcase/platforms/android/gradle.py | 48 ++++-
.../android_sdk/ADB/test_start_app.py | 53 ++++-
tests/platforms/android/gradle/test_create.py | 8 +
tests/platforms/android/gradle/test_run.py | 200 +++++++++++++++++-
6 files changed, 311 insertions(+), 13 deletions(-)
create mode 100644 changes/2351.feature.md
diff --git a/changes/2351.feature.md b/changes/2351.feature.md
new file mode 100644
index 000000000..ab10bf3b9
--- /dev/null
+++ b/changes/2351.feature.md
@@ -0,0 +1 @@
+Added a `--debug` option to the `build` and `run` commands for Android.
diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py
index 0375f0f41..6966a5b4d 100644
--- a/src/briefcase/integrations/android_sdk.py
+++ b/src/briefcase/integrations/android_sdk.py
@@ -1523,7 +1523,9 @@ def force_stop_app(self, package: str):
f"Unable to force stop app {package} on {self.device}"
) from e
- def start_app(self, package: str, activity: str, passthrough: list[str]):
+ def start_app(
+ self, package: str, activity: str, passthrough: list[str], env: dict[str, str]
+ ):
"""Start an app, specified as a package name & activity name.
If you have an APK file, and you are not sure of the package or activity
@@ -1533,6 +1535,7 @@ def start_app(self, package: str, activity: str, passthrough: list[str]):
:param package: The name of the Android package, e.g., com.username.myapp.
:param activity: The activity of the APK to start.
:param passthrough: Arguments to pass to the app.
+ :param env: Environment variables to pass to the app.
:returns: `None` on success; raises an exception on failure.
"""
try:
@@ -1552,6 +1555,15 @@ def start_app(self, package: str, activity: str, passthrough: list[str]):
"--es",
"org.beeware.ARGV",
shlex.quote(json.dumps(passthrough)), # Protect from Android's shell
+ *(
+ [
+ "--es",
+ "org.beeware.ENVIRON",
+ shlex.quote(json.dumps(env)), # Protect from Android's shell
+ ]
+ if env
+ else []
+ ),
)
# `adb shell am start` always exits with status zero. We look for error
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index b32382ea7..e973a29dc 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -19,6 +19,10 @@
)
from briefcase.config import AppConfig, parsed_version
from briefcase.console import ANSI_ESC_SEQ_RE_DEF
+from briefcase.debuggers.base import (
+ AppPackagesPathMappings,
+ DebuggerConnectionMode,
+)
from briefcase.exceptions import BriefcaseCommandError
from briefcase.integrations.android_sdk import ADB, AndroidSDK
from briefcase.integrations.subprocess import SubprocessArgT
@@ -216,15 +220,19 @@ def output_format_template_context(self, app: AppConfig):
"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0",
]
+ # Extract test packages, to enable features like test discovery and assertion rewriting.
+ extract_sources = app.test_sources or []
+
+ # In debug mode extract all source packages so that the debugger can get the source code
+ # at runtime (eg. via 'll' in pdb).
+ if app.debugger:
+ extract_sources.extend(app.sources)
+
return {
"version_code": version_code,
"safe_formal_name": safe_formal_name(app.formal_name),
- # Extract test packages to enable features like test discovery and assertion
- # rewriting.
"extract_packages": ", ".join(
- f'"{name}"'
- for path in (app.test_sources or [])
- if (name := Path(path).name)
+ [f'"{name}"' for path in extract_sources if (name := Path(path).name)]
),
"build_gradle_dependencies": {"implementation": dependencies},
}
@@ -288,6 +296,7 @@ def permissions_context(self, app: AppConfig, x_permissions: dict[str, str]):
class GradleUpdateCommand(GradleCreateCommand, UpdateCommand):
description = "Update an existing Android Gradle project."
+ supports_debugger = True
class GradleOpenCommand(GradleMixin, OpenCommand):
@@ -296,6 +305,7 @@ class GradleOpenCommand(GradleMixin, OpenCommand):
class GradleBuildCommand(GradleMixin, BuildCommand):
description = "Build an Android debug APK."
+ supports_debugger = True
def metadata_resource_path(self, app: AppConfig):
return self.bundle_path(app) / self.path_index(app, "metadata_resource_path")
@@ -333,6 +343,7 @@ def build_app(self, app: AppConfig, **kwargs):
class GradleRunCommand(GradleMixin, RunCommand):
description = "Run an Android debug APK on a device (physical or virtual)."
+ supports_debugger = True
def verify_tools(self):
super().verify_tools()
@@ -378,6 +389,20 @@ def add_options(self, parser):
help="Reverse the specified port from device to host.",
)
+ def debugger_app_packages_path_mapping(
+ self, app: AppConfig
+ ) -> AppPackagesPathMappings:
+ """Get the path mappings for the app packages.
+
+ :param app: The config object for the app
+ :returns: The path mappings for the app packages
+ """
+ app_packages_path = self.bundle_path(app) / "app/build/python/pip/debug/common"
+ return AppPackagesPathMappings(
+ sys_path_regex="requirements$",
+ host_folder=f"{app_packages_path}",
+ )
+
def run_app(
self,
app: AppConfig,
@@ -450,6 +475,17 @@ def run_app(
forward_ports = forward_ports or []
reverse_ports = reverse_ports or []
+ env = {}
+ if self.console.is_debug:
+ env["BRIEFCASE_DEBUG"] = "1"
+
+ if app.debugger:
+ env["BRIEFCASE_DEBUGGER"] = app.debugger.get_env_config(self, app)
+ if app.debugger.connection_mode == DebuggerConnectionMode.SERVER:
+ forward_ports.append(app.debugger_port)
+ else:
+ reverse_ports.append(app.debugger_port)
+
# Forward/Reverse requested ports
with self.forward_ports(adb, forward_ports, reverse_ports):
# To start the app, we launch `org.beeware.android.MainActivity`.
@@ -458,7 +494,7 @@ def run_app(
device_start_time = adb.datetime()
adb.start_app(
- package, "org.beeware.android.MainActivity", passthrough
+ package, "org.beeware.android.MainActivity", passthrough, env
)
# Try to get the PID for 5 seconds.
diff --git a/tests/integrations/android_sdk/ADB/test_start_app.py b/tests/integrations/android_sdk/ADB/test_start_app.py
index 17788a0dc..1c6a0d525 100644
--- a/tests/integrations/android_sdk/ADB/test_start_app.py
+++ b/tests/integrations/android_sdk/ADB/test_start_app.py
@@ -30,7 +30,7 @@ def test_start_app_launches_app(adb, capsys, passthrough):
# Invoke start_app
adb.start_app(
- "com.example.sample.package", "com.example.sample.activity", passthrough
+ "com.example.sample.package", "com.example.sample.activity", passthrough, {}
)
# Validate call parameters.
@@ -54,6 +54,45 @@ def test_start_app_launches_app(adb, capsys, passthrough):
assert "normal adb output" not in capsys.readouterr()
+@pytest.mark.parametrize(
+ "env",
+ [
+ {"PARAM1": "VALUE1"},
+ {"BRIEFCASE_DEBUGGER": '{"host": "localhost", "port": 1234}'},
+ ],
+)
+def test_start_app_launches_app_with_env(adb, capsys, env):
+ """Invoking `start_app()` calls `run()` with the appropriate parameters."""
+ # Mock out the run command on an adb instance
+ adb.run = MagicMock(return_value="example normal adb output")
+
+ # Invoke start_app
+ adb.start_app("com.example.sample.package", "com.example.sample.activity", [], env)
+
+ # Validate call parameters.
+ adb.run.assert_called_once_with(
+ "shell",
+ "am",
+ "start",
+ "-n",
+ "com.example.sample.package/com.example.sample.activity",
+ "-a",
+ "android.intent.action.MAIN",
+ "-c",
+ "android.intent.category.LAUNCHER",
+ "--es",
+ "org.beeware.ARGV",
+ "'[]'",
+ "--es",
+ "org.beeware.ENVIRON",
+ shlex.quote(json.dumps(env)),
+ )
+
+ # Validate that the normal output of the command was not printed (since there
+ # was no error).
+ assert "normal adb output" not in capsys.readouterr()
+
+
def test_missing_activity(adb):
"""If the activity doesn't exist, the error is caught."""
# Use real `adb` output from launching an activity that does not exist.
@@ -69,7 +108,9 @@ def test_missing_activity(adb):
)
with pytest.raises(BriefcaseCommandError) as exc_info:
- adb.start_app("com.example.sample.package", "com.example.sample.activity", [])
+ adb.start_app(
+ "com.example.sample.package", "com.example.sample.activity", [], {}
+ )
assert "Activity class not found" in str(exc_info.value)
@@ -81,7 +122,9 @@ def test_invalid_device(adb):
adb.run = MagicMock(side_effect=InvalidDeviceError("device", "exampleDevice"))
with pytest.raises(InvalidDeviceError):
- adb.start_app("com.example.sample.package", "com.example.sample.activity", [])
+ adb.start_app(
+ "com.example.sample.package", "com.example.sample.activity", [], {}
+ )
def test_unable_to_start(adb):
@@ -92,4 +135,6 @@ def test_unable_to_start(adb):
BriefcaseCommandError,
match=r"Unable to start com.example.sample.package/com.example.sample.activity on exampleDevice",
):
- adb.start_app("com.example.sample.package", "com.example.sample.activity", [])
+ adb.start_app(
+ "com.example.sample.package", "com.example.sample.activity", [], {}
+ )
diff --git a/tests/platforms/android/gradle/test_create.py b/tests/platforms/android/gradle/test_create.py
index a5cbcd8d1..1b82825a0 100644
--- a/tests/platforms/android/gradle/test_create.py
+++ b/tests/platforms/android/gradle/test_create.py
@@ -200,6 +200,14 @@ def test_extract_packages(create_command, first_app_config, test_sources, expect
assert context["extract_packages"] == expected
+def test_extract_packages_debugger(create_command, first_app_config, dummy_debugger):
+ first_app_config.test_sources = ["one", "two", "three"]
+ first_app_config.sources = ["four", "five", "six"]
+ first_app_config.debugger = dummy_debugger
+ context = create_command.output_format_template_context(first_app_config)
+ assert context["extract_packages"] == '"one", "two", "three", "four", "five", "six"'
+
+
@pytest.mark.parametrize(
("permissions", "features", "context"),
[
diff --git a/tests/platforms/android/gradle/test_run.py b/tests/platforms/android/gradle/test_run.py
index 7a2a3ba5d..f954c3100 100644
--- a/tests/platforms/android/gradle/test_run.py
+++ b/tests/platforms/android/gradle/test_run.py
@@ -1,4 +1,5 @@
import datetime
+import json
import os
import platform
import sys
@@ -9,6 +10,8 @@
import httpx
import pytest
+from briefcase.console import LogLevel
+from briefcase.debuggers.base import BaseDebugger, DebuggerConnectionMode
from briefcase.exceptions import BriefcaseCommandError
from briefcase.integrations.android_sdk import ADB, AndroidSDK
from briefcase.integrations.java import JDK
@@ -89,6 +92,9 @@ def test_device_option(run_command):
"update_stub": False,
"no_update": False,
"test_mode": False,
+ "debugger": None,
+ "debugger_host": "localhost",
+ "debugger_port": 5678,
"passthrough": [],
"extra_emulator_args": None,
"shutdown_on_exit": False,
@@ -114,6 +120,9 @@ def test_extra_emulator_args_option(run_command):
"update_stub": False,
"no_update": False,
"test_mode": False,
+ "debugger": None,
+ "debugger_host": "localhost",
+ "debugger_port": 5678,
"passthrough": [],
"extra_emulator_args": ["-no-window", "-no-audio"],
"shutdown_on_exit": False,
@@ -137,6 +146,9 @@ def test_shutdown_on_exit_option(run_command):
"update_stub": False,
"no_update": False,
"test_mode": False,
+ "debugger": None,
+ "debugger_host": "localhost",
+ "debugger_port": 5678,
"passthrough": [],
"extra_emulator_args": None,
"shutdown_on_exit": True,
@@ -163,6 +175,9 @@ def test_forward_ports_option(run_command):
"no_update": False,
"test_mode": False,
"passthrough": [],
+ "debugger": None,
+ "debugger_host": "localhost",
+ "debugger_port": 5678,
"extra_emulator_args": None,
"shutdown_on_exit": False,
"forward_ports": [80, 81],
@@ -188,6 +203,9 @@ def test_reverse_ports_option(run_command):
"no_update": False,
"test_mode": False,
"passthrough": [],
+ "debugger": None,
+ "debugger_host": "localhost",
+ "debugger_port": 5678,
"extra_emulator_args": None,
"shutdown_on_exit": False,
"forward_ports": None,
@@ -247,6 +265,8 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=[],
)
@@ -273,6 +293,7 @@ def mock_stream_output(app, stop_func, **kwargs):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
run_command.tools.mock_adb.forward_remove.assert_not_called()
@@ -323,6 +344,8 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=["foo", "--bar"],
)
@@ -346,6 +369,7 @@ def mock_stream_output(app, stop_func, **kwargs):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
["foo", "--bar"],
+ {},
)
run_command.tools.mock_adb.pidof.assert_called_once_with(
@@ -395,6 +419,7 @@ def test_run_forward_reverse_ports(run_command, first_app_config):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
assert run_command.tools.mock_adb.forward_remove.mock_calls == [
@@ -424,6 +449,8 @@ def test_run_slow_start(run_command, first_app_config, monkeypatch):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=[],
)
@@ -476,6 +503,8 @@ def test_run_crash_at_start(run_command, first_app_config, monkeypatch):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=[],
)
@@ -513,7 +542,9 @@ def test_run_created_emulator(run_command, first_app_config):
run_command.tools.mock_adb.logcat.return_value = log_popen
# Invoke run_app
- run_command.run_app(first_app_config, passthrough=[])
+ run_command.run_app(
+ first_app_config, debugger_host=None, debugger_port=None, passthrough=[]
+ )
# A new emulator was created
run_command.tools.android_sdk.create_emulator.assert_called_once_with()
@@ -542,6 +573,7 @@ def test_run_created_emulator(run_command, first_app_config):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
run_command.tools.mock_adb.logcat.assert_called_once_with(pid="777")
@@ -575,7 +607,9 @@ def test_run_idle_device(run_command, first_app_config):
run_command.tools.mock_adb.logcat.return_value = log_popen
# Invoke run_app
- run_command.run_app(first_app_config, passthrough=[])
+ run_command.run_app(
+ first_app_config, debugger_host=None, debugger_port=None, passthrough=[]
+ )
# No attempt was made to create a new emulator
run_command.tools.android_sdk.create_emulator.assert_not_called()
@@ -603,6 +637,7 @@ def test_run_idle_device(run_command, first_app_config):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
run_command.tools.mock_adb.logcat.assert_called_once_with(pid="777")
@@ -680,6 +715,8 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=[],
shutdown_on_exit=True,
)
@@ -704,6 +741,7 @@ def mock_stream_output(app, stop_func, **kwargs):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
run_command.tools.mock_adb.pidof.assert_called_once_with(
@@ -753,6 +791,8 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
+ debugger_host=None,
+ debugger_port=None,
passthrough=["foo", "--bar"],
shutdown_on_exit=True,
)
@@ -777,6 +817,7 @@ def mock_stream_output(app, stop_func, **kwargs):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
["foo", "--bar"],
+ {},
)
run_command.tools.mock_adb.pidof.assert_called_once_with(
@@ -821,6 +862,8 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
# Invoke run_app
run_command.run_app(
first_app_config,
+ debugger_host=None,
+ debugger_port=None,
passthrough=[],
extra_emulator_args=["-no-window", "-no-audio"],
shutdown_on_exit=True,
@@ -854,6 +897,7 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
f"{first_app_config.package_name}.{first_app_config.module_name}",
"org.beeware.android.MainActivity",
[],
+ {},
)
run_command.tools.mock_adb.logcat.assert_called_once_with(pid="777")
@@ -869,3 +913,155 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
# The emulator was killed at the end of the test
run_command.tools.mock_adb.kill.assert_called_once_with()
+
+
+class ServerDebugger(BaseDebugger):
+ @property
+ def name(self) -> str:
+ return "dummy"
+
+ @property
+ def connection_mode(self) -> DebuggerConnectionMode:
+ return DebuggerConnectionMode.SERVER
+
+ @property
+ def debugger_support_pkg(self) -> str:
+ raise NotImplementedError
+
+
+class ClientDebugger(BaseDebugger):
+ @property
+ def name(self) -> str:
+ return "dummy"
+
+ @property
+ def connection_mode(self) -> DebuggerConnectionMode:
+ return DebuggerConnectionMode.CLIENT
+
+ @property
+ def debugger_support_pkg(self) -> str:
+ raise NotImplementedError
+
+
+@pytest.mark.parametrize(
+ "debugger",
+ [
+ ServerDebugger(),
+ ClientDebugger(),
+ ],
+)
+def test_run_debugger(run_command, first_app_config, tmp_path, debugger):
+ """An app can be run in debug mode."""
+ run_command.console.verbosity = LogLevel.DEBUG
+
+ # Set up device selection to return a running physical device.
+ run_command.tools.android_sdk.select_target_device = mock.MagicMock(
+ return_value=("exampleDevice", "ExampleDevice", None)
+ )
+
+ # Set up the log streamer to return a known stream
+ log_popen = mock.MagicMock()
+ run_command.tools.mock_adb.logcat.return_value = log_popen
+
+ # To satisfy coverage, the stop function must be invoked at least once
+ # when invoking stream_output.
+ def mock_stream_output(app, stop_func, **kwargs):
+ stop_func()
+
+ run_command._stream_app_logs.side_effect = mock_stream_output
+
+ # Set up app config to have a `-` in the `bundle`, to ensure it gets
+ # normalized into a `_` via `package_name`.
+ first_app_config.bundle = "com.ex-ample"
+
+ # Set up the debugger
+ first_app_config.debugger = debugger
+ first_app_config.debugger_host = "somehost"
+ first_app_config.debugger_port = 9999
+
+ # Invoke run_app with args.
+ run_command.run_app(
+ first_app_config,
+ device_or_avd="exampleDevice",
+ passthrough=[],
+ )
+
+ # select_target_device was invoked with a specific device
+ run_command.tools.android_sdk.select_target_device.assert_called_once_with(
+ "exampleDevice"
+ )
+
+ # The ADB wrapper is created
+ run_command.tools.android_sdk.adb.assert_called_once_with(device="exampleDevice")
+
+ # The adb wrapper is invoked with the expected arguments
+ run_command.tools.mock_adb.install_apk.assert_called_once_with(
+ run_command.binary_path(first_app_config)
+ )
+ run_command.tools.mock_adb.force_stop_app.assert_called_once_with(
+ f"{first_app_config.package_name}.{first_app_config.module_name}",
+ )
+
+ run_command.tools.mock_adb.start_app.assert_called_once_with(
+ f"{first_app_config.package_name}.{first_app_config.module_name}",
+ "org.beeware.android.MainActivity",
+ [],
+ {
+ "BRIEFCASE_DEBUG": "1",
+ "BRIEFCASE_DEBUGGER": json.dumps(
+ {
+ "debugger": "dummy",
+ "host": "somehost",
+ "port": 9999,
+ "host_os": platform.system(),
+ "app_path_mappings": {
+ "device_sys_path_regex": "app$",
+ "device_subfolders": ["first_app"],
+ "host_folders": [str(tmp_path / "base_path/src/first_app")],
+ },
+ "app_packages_path_mappings": {
+ "sys_path_regex": "requirements$",
+ "host_folder": str(
+ tmp_path
+ / "base_path/build/first-app/android/gradle/app/build/python/pip/debug/common"
+ ),
+ },
+ }
+ ),
+ },
+ )
+
+ run_command.tools.mock_adb.pidof.assert_called_once_with(
+ f"{first_app_config.package_name}.{first_app_config.module_name}",
+ quiet=2,
+ )
+ run_command.tools.mock_adb.logcat.assert_called_once_with(pid="777")
+
+ if isinstance(debugger, ServerDebugger):
+ run_command.tools.mock_adb.forward.assert_called_once_with(
+ 9999,
+ 9999,
+ )
+ run_command.tools.mock_adb.forward_remove.assert_called_once_with(
+ 9999,
+ )
+ elif isinstance(debugger, ClientDebugger):
+ run_command.tools.mock_adb.reverse.assert_called_once_with(
+ 9999,
+ 9999,
+ )
+ run_command.tools.mock_adb.reverse_remove.assert_called_once_with(
+ 9999,
+ )
+
+ run_command._stream_app_logs.assert_called_once_with(
+ first_app_config,
+ popen=log_popen,
+ clean_filter=android_log_clean_filter,
+ clean_output=False,
+ stop_func=mock.ANY,
+ log_stream=True,
+ )
+
+ # The emulator was not killed at the end of the test
+ run_command.tools.mock_adb.kill.assert_not_called()
From 1f3a2d41798dbbc16b0ef4e5cf42895ed8936d08 Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Tue, 11 Nov 2025 23:00:02 +0100
Subject: [PATCH 02/14] add comment, that it is also necessary for setting
breakpoints in vscode
---
src/briefcase/platforms/android/gradle.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index e973a29dc..097275e24 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -224,7 +224,7 @@ def output_format_template_context(self, app: AppConfig):
extract_sources = app.test_sources or []
# In debug mode extract all source packages so that the debugger can get the source code
- # at runtime (eg. via 'll' in pdb).
+ # at runtime. This is necessary for setting breakpoints in VSCode or when using 'll' in pdb.
if app.debugger:
extract_sources.extend(app.sources)
From 7ef2c1f29e966b26ea0a464a49b4b6380dcef4cd Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Sun, 16 Nov 2025 21:37:31 +0100
Subject: [PATCH 03/14] set packages to extract in build command and extract
all packages
---
src/briefcase/platforms/android/gradle.py | 33 ++++--
tests/platforms/android/gradle/conftest.py | 13 +++
tests/platforms/android/gradle/test_build.py | 104 ++++++++++++++++++
tests/platforms/android/gradle/test_create.py | 54 ---------
4 files changed, 139 insertions(+), 65 deletions(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index 097275e24..fa43175a7 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -220,20 +220,9 @@ def output_format_template_context(self, app: AppConfig):
"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0",
]
- # Extract test packages, to enable features like test discovery and assertion rewriting.
- extract_sources = app.test_sources or []
-
- # In debug mode extract all source packages so that the debugger can get the source code
- # at runtime. This is necessary for setting breakpoints in VSCode or when using 'll' in pdb.
- if app.debugger:
- extract_sources.extend(app.sources)
-
return {
"version_code": version_code,
"safe_formal_name": safe_formal_name(app.formal_name),
- "extract_packages": ", ".join(
- [f'"{name}"' for path in extract_sources if (name := Path(path).name)]
- ),
"build_gradle_dependencies": {"implementation": dependencies},
}
@@ -310,6 +299,11 @@ class GradleBuildCommand(GradleMixin, BuildCommand):
def metadata_resource_path(self, app: AppConfig):
return self.bundle_path(app) / self.path_index(app, "metadata_resource_path")
+ def metadata_extract_packages_path(self, app: AppConfig):
+ return self.bundle_path(app) / self.path_index(
+ app, "metadata_extract_packages_path"
+ )
+
def update_app_metadata(self, app: AppConfig):
with (
self.console.wait_bar("Setting main module..."),
@@ -325,6 +319,23 @@ def update_app_metadata(self, app: AppConfig):
"""
)
+ with (
+ self.console.wait_bar("Setting packages to extract..."),
+ self.metadata_extract_packages_path(app).open("w", encoding="utf-8") as f,
+ ):
+ if app.debugger:
+ # In debug mode extract all source packages so that the debugger can get the source code
+ # at runtime. This is necessary for setting breakpoints in VSCode.
+ extract_packages = ["*"]
+ else:
+ # Extract test packages, to enable features like test discovery and assertion rewriting.
+ extract_sources = app.test_sources or []
+ extract_packages = [
+ name for path in extract_sources if (name := Path(path).name)
+ ]
+
+ f.write("\n".join(extract_packages))
+
def build_app(self, app: AppConfig, **kwargs):
"""Build an application.
diff --git a/tests/platforms/android/gradle/conftest.py b/tests/platforms/android/gradle/conftest.py
index cef53de31..8bbf666c6 100644
--- a/tests/platforms/android/gradle/conftest.py
+++ b/tests/platforms/android/gradle/conftest.py
@@ -50,6 +50,7 @@ def first_app_generated(first_app_config, tmp_path):
app_packages_path="app_packages"
support_path="support"
metadata_resource_path="res/briefcase.xml"
+metadata_extract_packages_path = "app/extract-packages.txt"
""",
)
@@ -64,4 +65,16 @@ def first_app_generated(first_app_config, tmp_path):
/ "briefcase.xml",
"""""",
)
+
+ create_file(
+ tmp_path
+ / "base_path"
+ / "build"
+ / "first-app"
+ / "android"
+ / "gradle"
+ / "app"
+ / "extract-packages.txt",
+ "something-to-be-overwritten",
+ )
return first_app_config
diff --git a/tests/platforms/android/gradle/test_build.py b/tests/platforms/android/gradle/test_build.py
index c118701ae..fd32d1cd2 100644
--- a/tests/platforms/android/gradle/test_build.py
+++ b/tests/platforms/android/gradle/test_build.py
@@ -115,6 +115,18 @@ def test_build_app(
+ "\n"
)
+ with (
+ tmp_path
+ / "base_path"
+ / "build"
+ / "first-app"
+ / "android"
+ / "gradle"
+ / "app"
+ / "extract-packages.txt"
+ ).open(encoding="utf-8") as f:
+ assert f.read() == ""
+
@pytest.mark.parametrize(
("host_os", "gradlew_name", "debug_mode"),
@@ -135,6 +147,7 @@ def test_build_app_test_mode(
):
"""The app can be built in test mode, invoking gradle and rewriting app metadata."""
first_app_generated.test_mode = True
+ first_app_generated.test_sources = ["my_test_package"]
# Mock out `host_os` so we can validate which name is used for gradlew.
build_command.tools.host_os = host_os
@@ -184,6 +197,97 @@ def test_build_app_test_mode(
+ "\n"
)
+ with (
+ tmp_path
+ / "base_path"
+ / "build"
+ / "first-app"
+ / "android"
+ / "gradle"
+ / "app"
+ / "extract-packages.txt"
+ ).open(encoding="utf-8") as f:
+ assert f.read() == "my_test_package"
+
+
+extract_packages_params = [
+ ([], ""),
+ ([""], ""),
+ (["one"], "one"),
+ (["one/two"], "two"),
+ (["one//two"], "two"),
+ (["one/two/three"], "three"),
+ (["one", "two"], "one\ntwo"),
+ (["one", "two", "three"], "one\ntwo\nthree"),
+ (["one/two", "three/four"], "two\nfour"),
+ (["/leading"], "leading"),
+ (["/leading/two"], "two"),
+ (["/leading/two/three"], "three"),
+ (["trailing/"], "trailing"),
+ (["trailing//"], "trailing"),
+ (["trailing/two/"], "two"),
+]
+
+# Handle differences in UNC path parsing (https://github.com/python/cpython/pull/100351).
+extract_packages_params += [
+ (
+ ["//leading"],
+ "" if sys.platform == "win32" and sys.version_info >= (3, 12) else "leading",
+ ),
+ (
+ ["//leading/two"],
+ "" if sys.platform == "win32" else "two",
+ ),
+ (["//leading/two/three"], "three"),
+ (["//leading/two/three/four"], "four"),
+]
+
+if sys.platform == "win32":
+ extract_packages_params += [
+ ([path.replace("/", "\\") for path in test_sources], expected)
+ for test_sources, expected in extract_packages_params
+ ]
+
+
+@pytest.mark.parametrize(("test_sources", "expected"), extract_packages_params)
+def test_extract_packages(
+ build_command, first_app_generated, test_sources, expected, tmp_path
+):
+ first_app_generated.test_sources = test_sources
+ build_command.update_app_metadata(first_app_generated)
+
+ with (
+ tmp_path
+ / "base_path"
+ / "build"
+ / "first-app"
+ / "android"
+ / "gradle"
+ / "app"
+ / "extract-packages.txt"
+ ).open(encoding="utf-8") as f:
+ assert f.read() == expected
+
+
+def test_extract_packages_debugger(
+ build_command, first_app_generated, dummy_debugger, tmp_path
+):
+ first_app_generated.test_sources = ["one", "two", "three"]
+ first_app_generated.debugger = dummy_debugger
+ build_command.update_app_metadata(first_app_generated)
+
+ with (
+ tmp_path
+ / "base_path"
+ / "build"
+ / "first-app"
+ / "android"
+ / "gradle"
+ / "app"
+ / "extract-packages.txt"
+ ).open(encoding="utf-8") as f:
+ assert f.read() == "*"
+
def test_print_gradle_errors(build_command, first_app_generated):
"""Validate that build_app() will convert stderr/stdout from the process into
diff --git a/tests/platforms/android/gradle/test_create.py b/tests/platforms/android/gradle/test_create.py
index 1b82825a0..8055e7237 100644
--- a/tests/platforms/android/gradle/test_create.py
+++ b/tests/platforms/android/gradle/test_create.py
@@ -154,60 +154,6 @@ def test_build_gradle_dependencies(
) == has_warning
-extract_packages_params = [
- ([], ""),
- ([""], ""),
- (["one"], '"one"'),
- (["one/two"], '"two"'),
- (["one//two"], '"two"'),
- (["one/two/three"], '"three"'),
- (["one", "two"], '"one", "two"'),
- (["one", "two", "three"], '"one", "two", "three"'),
- (["one/two", "three/four"], '"two", "four"'),
- (["/leading"], '"leading"'),
- (["/leading/two"], '"two"'),
- (["/leading/two/three"], '"three"'),
- (["trailing/"], '"trailing"'),
- (["trailing//"], '"trailing"'),
- (["trailing/two/"], '"two"'),
-]
-
-# Handle differences in UNC path parsing (https://github.com/python/cpython/pull/100351).
-extract_packages_params += [
- (
- ["//leading"],
- "" if sys.platform == "win32" and sys.version_info >= (3, 12) else '"leading"',
- ),
- (
- ["//leading/two"],
- "" if sys.platform == "win32" else '"two"',
- ),
- (["//leading/two/three"], '"three"'),
- (["//leading/two/three/four"], '"four"'),
-]
-
-if sys.platform == "win32":
- extract_packages_params += [
- ([path.replace("/", "\\") for path in test_sources], expected)
- for test_sources, expected in extract_packages_params
- ]
-
-
-@pytest.mark.parametrize(("test_sources", "expected"), extract_packages_params)
-def test_extract_packages(create_command, first_app_config, test_sources, expected):
- first_app_config.test_sources = test_sources
- context = create_command.output_format_template_context(first_app_config)
- assert context["extract_packages"] == expected
-
-
-def test_extract_packages_debugger(create_command, first_app_config, dummy_debugger):
- first_app_config.test_sources = ["one", "two", "three"]
- first_app_config.sources = ["four", "five", "six"]
- first_app_config.debugger = dummy_debugger
- context = create_command.output_format_template_context(first_app_config)
- assert context["extract_packages"] == '"one", "two", "three", "four", "five", "six"'
-
-
@pytest.mark.parametrize(
("permissions", "features", "context"),
[
From a3b998524c4123be0fd94545085948ae2bc93c4e Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Sun, 16 Nov 2025 22:55:13 +0100
Subject: [PATCH 04/14] correct comment
---
src/briefcase/platforms/android/gradle.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index fa43175a7..86bff09c1 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -324,8 +324,8 @@ def update_app_metadata(self, app: AppConfig):
self.metadata_extract_packages_path(app).open("w", encoding="utf-8") as f,
):
if app.debugger:
- # In debug mode extract all source packages so that the debugger can get the source code
- # at runtime. This is necessary for setting breakpoints in VSCode.
+ # In debug mode include the .py files and extract all of them so that the debugger can get
+ # the source code at runtime. This is e.g. necessary for setting breakpoints in VS Code.
extract_packages = ["*"]
else:
# Extract test packages, to enable features like test discovery and assertion rewriting.
From c6ee46dfb8fc2bb3cda9a2990892f3d9b8182495 Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Fri, 28 Nov 2025 21:00:02 +0100
Subject: [PATCH 05/14] change name to reflect the current changes in the
template repo
---
src/briefcase/platforms/android/gradle.py | 8 +++-----
tests/platforms/android/gradle/conftest.py | 2 +-
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index 22449b91b..b2f16b35b 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -325,10 +325,8 @@ class GradleBuildCommand(GradleMixin, BuildCommand):
def metadata_resource_path(self, app: AppConfig):
return self.bundle_path(app) / self.path_index(app, "metadata_resource_path")
- def metadata_extract_packages_path(self, app: AppConfig):
- return self.bundle_path(app) / self.path_index(
- app, "metadata_extract_packages_path"
- )
+ def extract_packages_path(self, app: AppConfig):
+ return self.bundle_path(app) / self.path_index(app, "extract_packages_path")
def update_app_metadata(self, app: AppConfig):
with (
@@ -347,7 +345,7 @@ def update_app_metadata(self, app: AppConfig):
with (
self.console.wait_bar("Setting packages to extract..."),
- self.metadata_extract_packages_path(app).open("w", encoding="utf-8") as f,
+ self.extract_packages_path(app).open("w", encoding="utf-8") as f,
):
if app.debugger:
# In debug mode include the .py files and extract all of them so that the debugger can get
diff --git a/tests/platforms/android/gradle/conftest.py b/tests/platforms/android/gradle/conftest.py
index 8bbf666c6..6b72b1db7 100644
--- a/tests/platforms/android/gradle/conftest.py
+++ b/tests/platforms/android/gradle/conftest.py
@@ -50,7 +50,7 @@ def first_app_generated(first_app_config, tmp_path):
app_packages_path="app_packages"
support_path="support"
metadata_resource_path="res/briefcase.xml"
-metadata_extract_packages_path = "app/extract-packages.txt"
+extract_packages_path = "app/extract-packages.txt"
""",
)
From 8cd21712cf7424fa5a2603cb0bebafc4be995296 Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Fri, 28 Nov 2025 21:14:49 +0100
Subject: [PATCH 06/14] fix platform target
---
src/briefcase/platforms/android/gradle.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index b2f16b35b..cd9c48a15 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -73,7 +73,7 @@ def android_log_clean_filter(line):
class GradleMixin:
output_format = "gradle"
platform = "android"
- platform_target_version = "0.3.15"
+ platform_target_version = "0.3.26"
@property
def packaging_formats(self):
From 93a1ce3be422b9c2dd857accad41f7ecae90fbc1 Mon Sep 17 00:00:00 2001
From: timrid <6593626+timrid@users.noreply.github.com>
Date: Fri, 28 Nov 2025 21:19:46 +0100
Subject: [PATCH 07/14] fix line length
---
src/briefcase/platforms/android/gradle.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index cd9c48a15..5372e7fdd 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -348,11 +348,13 @@ def update_app_metadata(self, app: AppConfig):
self.extract_packages_path(app).open("w", encoding="utf-8") as f,
):
if app.debugger:
- # In debug mode include the .py files and extract all of them so that the debugger can get
- # the source code at runtime. This is e.g. necessary for setting breakpoints in VS Code.
+ # In debug mode include the .py files and extract all of them so
+ # that the debugger can get the source code at runtime. This is e.g.
+ # necessary for setting breakpoints in VS Code.
extract_packages = ["*"]
else:
- # Extract test packages, to enable features like test discovery and assertion rewriting.
+ # Extract test packages, to enable features like test discovery and
+ # assertion rewriting.
extract_sources = app.test_sources or []
extract_packages = [
name for path in extract_sources if (name := Path(path).name)
From a544698816ba0c1cd72037f5cc1000ce585a4575 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Thu, 4 Dec 2025 14:10:04 +0100
Subject: [PATCH 08/14] fixed typo
---
docs/en/how-to/debugging/pdb.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/en/how-to/debugging/pdb.md b/docs/en/how-to/debugging/pdb.md
index 7e2fe642f..4c28dbc6c 100644
--- a/docs/en/how-to/debugging/pdb.md
+++ b/docs/en/how-to/debugging/pdb.md
@@ -16,7 +16,7 @@ This is currently an **experimental feature** that is only supported on Windows,
To debug a bundled app, add `breakpoint()` somewhere in your code where the debugger should halt.
-Your app must then be modified to include a bootstrap that will connect to the VS Code debugger. This is done by passing the `--debug debugpy` option to `briefcase build`:
+Your app must then be modified to include a bootstrap that will connect to the VS Code debugger. This is done by passing the `--debug pdb` option to `briefcase build`:
```console
$ briefcase build --debug pdb
From 95daf27b868c21682175ff8e3e12826ed3cd5791 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Thu, 4 Dec 2025 14:14:11 +0100
Subject: [PATCH 09/14] This PR has not made it in briefcase v0.3.26. So
prepare it for v0.3.27.
---
src/briefcase/platforms/android/gradle.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index 5372e7fdd..cc64ce970 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -73,7 +73,7 @@ def android_log_clean_filter(line):
class GradleMixin:
output_format = "gradle"
platform = "android"
- platform_target_version = "0.3.26"
+ platform_target_version = "0.3.27"
@property
def packaging_formats(self):
From ad1d88a240f3d548e332f938149baec22268c706 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Thu, 4 Dec 2025 14:18:11 +0100
Subject: [PATCH 10/14] Add Android as supported in the docs
---
docs/en/how-to/debugging/pdb.md | 2 +-
docs/en/how-to/debugging/vscode.md | 2 +-
docs/en/reference/commands/build.md | 2 +-
docs/en/reference/commands/run.md | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/en/how-to/debugging/pdb.md b/docs/en/how-to/debugging/pdb.md
index 4c28dbc6c..4668aec1b 100644
--- a/docs/en/how-to/debugging/pdb.md
+++ b/docs/en/how-to/debugging/pdb.md
@@ -10,7 +10,7 @@ To debug an app in development mode, add `breakpoint()` to your code somewhere t
/// warning | Note
-This is currently an **experimental feature** that is only supported on Windows, macOS and iOS.
+This is currently an **experimental feature** that is only supported on Windows, macOS, iOS and Android.
///
diff --git a/docs/en/how-to/debugging/vscode.md b/docs/en/how-to/debugging/vscode.md
index 49fecb915..5d34fb7ee 100644
--- a/docs/en/how-to/debugging/vscode.md
+++ b/docs/en/how-to/debugging/vscode.md
@@ -40,7 +40,7 @@ To start a debug session, open the debug view in VS Code using the sidebar, sele
/// warning | Experimental feature
-This is currently an **experimental feature** that is only supported on Windows, macOS and iOS.
+This is currently an **experimental feature** that is only supported on Windows, macOS, iOS and Android.
///
diff --git a/docs/en/reference/commands/build.md b/docs/en/reference/commands/build.md
index 25863ff4a..b2d1ceea2 100644
--- a/docs/en/reference/commands/build.md
+++ b/docs/en/reference/commands/build.md
@@ -105,7 +105,7 @@ Currently the following debuggers are supported:
If calling only `--debug` without selecting a debugger explicitly, `pdb` is used as default.
-This is an **experimental** new feature, that is currently only supported on Windows, macOS and iOS.
+This is an **experimental** new feature, that is currently only supported on Windows, macOS, iOS and Android.
This option may slow down the app a little bit.
diff --git a/docs/en/reference/commands/run.md b/docs/en/reference/commands/run.md
index 669ba9a11..aab0f5a77 100644
--- a/docs/en/reference/commands/run.md
+++ b/docs/en/reference/commands/run.md
@@ -109,7 +109,7 @@ Currently the following debuggers are supported:
If calling only `--debug` without selecting a debugger explicitly, `pdb` is used as default.
-This is an **experimental** new feature, that is currently only supported on Windows, macOS and iOS.
+This is an **experimental** new feature, that is currently only supported on Windows, macOS, iOS and Android.
The selected debugger in `run --debug ` has to match the selected debugger in `build --debug `.
From 77b3b815e29dfa46950bd143a3251cbb7d2a4222 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Fri, 5 Dec 2025 15:49:02 +0100
Subject: [PATCH 11/14] "target_epoch" is not defined. The correct string is
"target_version".
---
tests/platforms/android/gradle/test_build.py | 2 +-
tests/platforms/android/gradle/test_create.py | 2 +-
tests/platforms/android/gradle/test_open.py | 2 +-
tests/platforms/android/gradle/test_package__aab.py | 2 +-
tests/platforms/android/gradle/test_package__apk.py | 2 +-
tests/platforms/android/gradle/test_package__debug_apk.py | 2 +-
tests/platforms/android/gradle/test_run.py | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/tests/platforms/android/gradle/test_build.py b/tests/platforms/android/gradle/test_build.py
index fd32d1cd2..1e8b2bc68 100644
--- a/tests/platforms/android/gradle/test_build.py
+++ b/tests/platforms/android/gradle/test_build.py
@@ -37,7 +37,7 @@ def test_unsupported_template_version(build_command, first_app_generated):
build_command.verify_app = MagicMock(wraps=build_command.verify_app)
build_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_create.py b/tests/platforms/android/gradle/test_create.py
index 6db4aed9f..44832e213 100644
--- a/tests/platforms/android/gradle/test_create.py
+++ b/tests/platforms/android/gradle/test_create.py
@@ -38,7 +38,7 @@ def test_unsupported_template_version(create_command, first_app_config):
create_command.verify_app = MagicMock(wraps=create_command.verify_app)
create_command._briefcase_toml.update(
- {first_app_config: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_config: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_open.py b/tests/platforms/android/gradle/test_open.py
index eb1a14a29..011109a64 100644
--- a/tests/platforms/android/gradle/test_open.py
+++ b/tests/platforms/android/gradle/test_open.py
@@ -70,7 +70,7 @@ def test_unsupported_template_version(open_command, first_app_generated, tmp_pat
open_command.verify_app = MagicMock(wraps=open_command.verify_app)
open_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_package__aab.py b/tests/platforms/android/gradle/test_package__aab.py
index 07a737b40..85ed717ca 100644
--- a/tests/platforms/android/gradle/test_package__aab.py
+++ b/tests/platforms/android/gradle/test_package__aab.py
@@ -23,7 +23,7 @@ def test_unsupported_template_version(package_command, first_app_generated):
package_command.verify_app = MagicMock(wraps=package_command.verify_app)
package_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_package__apk.py b/tests/platforms/android/gradle/test_package__apk.py
index b3944ea99..48d0919a3 100644
--- a/tests/platforms/android/gradle/test_package__apk.py
+++ b/tests/platforms/android/gradle/test_package__apk.py
@@ -23,7 +23,7 @@ def test_unsupported_template_version(package_command, first_app_generated):
package_command.verify_app = MagicMock(wraps=package_command.verify_app)
package_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_package__debug_apk.py b/tests/platforms/android/gradle/test_package__debug_apk.py
index 23d7b75a1..a611a2f22 100644
--- a/tests/platforms/android/gradle/test_package__debug_apk.py
+++ b/tests/platforms/android/gradle/test_package__debug_apk.py
@@ -23,7 +23,7 @@ def test_unsupported_template_version(package_command, first_app_generated):
package_command.verify_app = MagicMock(wraps=package_command.verify_app)
package_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
diff --git a/tests/platforms/android/gradle/test_run.py b/tests/platforms/android/gradle/test_run.py
index f954c3100..91247e7d2 100644
--- a/tests/platforms/android/gradle/test_run.py
+++ b/tests/platforms/android/gradle/test_run.py
@@ -227,7 +227,7 @@ def test_unsupported_template_version(run_command, first_app_generated):
run_command.verify_app = mock.MagicMock(wraps=run_command.verify_app)
run_command._briefcase_toml.update(
- {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}}
+ {first_app_generated: {"briefcase": {"target_version": "0.3.16"}}}
)
with pytest.raises(
From 4637f793db330fba62972bc3a6f00540a5590bbb Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Fri, 5 Dec 2025 16:04:47 +0100
Subject: [PATCH 12/14] fix target_version in unit test
---
tests/platforms/android/gradle/test_open.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/platforms/android/gradle/test_open.py b/tests/platforms/android/gradle/test_open.py
index 011109a64..94f4e5abb 100644
--- a/tests/platforms/android/gradle/test_open.py
+++ b/tests/platforms/android/gradle/test_open.py
@@ -39,9 +39,9 @@ def open_command(dummy_console, tmp_path, first_app_config):
command.tools.subprocess = MagicMock(spec_set=Subprocess)
command.tools.file.download = MagicMock(spec_set=File.download)
- # Mock all apps as targeting version 0.3.15
+ # Mock all apps as targeting version 0.3.27
command._briefcase_toml = defaultdict(
- lambda: {"briefcase": {"target_version": "0.3.15"}}
+ lambda: {"briefcase": {"target_version": "0.3.27"}}
)
# Mock some OS calls needed to make the tools appear to exist
From cd8132c9b1dcbc01fe0b313eb72ecafa077d3c47 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Fri, 5 Dec 2025 17:23:47 +0100
Subject: [PATCH 13/14] some change to trigger pipeline.
---
src/briefcase/platforms/android/gradle.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py
index cc64ce970..f8fab4356 100644
--- a/src/briefcase/platforms/android/gradle.py
+++ b/src/briefcase/platforms/android/gradle.py
@@ -349,8 +349,8 @@ def update_app_metadata(self, app: AppConfig):
):
if app.debugger:
# In debug mode include the .py files and extract all of them so
- # that the debugger can get the source code at runtime. This is e.g.
- # necessary for setting breakpoints in VS Code.
+ # that the debugger can get the source code at runtime. This is
+ # e.g. necessary for setting breakpoints in VS Code.
extract_packages = ["*"]
else:
# Extract test packages, to enable features like test discovery and
From 36cd78ca268136e5f3d6e6ef6858ccaa43efa961 Mon Sep 17 00:00:00 2001
From: Tim Rid <6593626+timrid@users.noreply.github.com>
Date: Fri, 5 Dec 2025 17:50:46 +0100
Subject: [PATCH 14/14] remove unnecessary parameters
---
tests/platforms/android/gradle/test_run.py | 22 ++--------------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/tests/platforms/android/gradle/test_run.py b/tests/platforms/android/gradle/test_run.py
index 91247e7d2..8490c3e9a 100644
--- a/tests/platforms/android/gradle/test_run.py
+++ b/tests/platforms/android/gradle/test_run.py
@@ -265,8 +265,6 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=[],
)
@@ -344,8 +342,6 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=["foo", "--bar"],
)
@@ -449,8 +445,6 @@ def test_run_slow_start(run_command, first_app_config, monkeypatch):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=[],
)
@@ -503,8 +497,6 @@ def test_run_crash_at_start(run_command, first_app_config, monkeypatch):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=[],
)
@@ -542,9 +534,7 @@ def test_run_created_emulator(run_command, first_app_config):
run_command.tools.mock_adb.logcat.return_value = log_popen
# Invoke run_app
- run_command.run_app(
- first_app_config, debugger_host=None, debugger_port=None, passthrough=[]
- )
+ run_command.run_app(first_app_config, passthrough=[])
# A new emulator was created
run_command.tools.android_sdk.create_emulator.assert_called_once_with()
@@ -607,9 +597,7 @@ def test_run_idle_device(run_command, first_app_config):
run_command.tools.mock_adb.logcat.return_value = log_popen
# Invoke run_app
- run_command.run_app(
- first_app_config, debugger_host=None, debugger_port=None, passthrough=[]
- )
+ run_command.run_app(first_app_config, passthrough=[])
# No attempt was made to create a new emulator
run_command.tools.android_sdk.create_emulator.assert_not_called()
@@ -715,8 +703,6 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=[],
shutdown_on_exit=True,
)
@@ -791,8 +777,6 @@ def mock_stream_output(app, stop_func, **kwargs):
run_command.run_app(
first_app_config,
device_or_avd="exampleDevice",
- debugger_host=None,
- debugger_port=None,
passthrough=["foo", "--bar"],
shutdown_on_exit=True,
)
@@ -862,8 +846,6 @@ def test_run_test_mode_created_emulator(run_command, first_app_config):
# Invoke run_app
run_command.run_app(
first_app_config,
- debugger_host=None,
- debugger_port=None,
passthrough=[],
extra_emulator_args=["-no-window", "-no-audio"],
shutdown_on_exit=True,