From 95ee30345845e915f7be52687fd7409b55b712ee Mon Sep 17 00:00:00 2001 From: epba23 Date: Sat, 22 Jul 2023 16:18:52 +0200 Subject: [PATCH 01/10] refactored existing gradle tests --- src/briefcase/platforms/android/gradle.py | 2 +- tests/conftest.py | 1 + tests/platforms/android/gradle/conftest.py | 30 ++++ .../platforms/android/gradle/test_package.py | 142 +----------------- .../android/gradle/test_package__aab.py | 111 ++++++++++++++ .../android/gradle/test_package__apk.py | 135 +++++++++++++++++ 6 files changed, 279 insertions(+), 142 deletions(-) create mode 100644 tests/platforms/android/gradle/test_package__aab.py create mode 100644 tests/platforms/android/gradle/test_package__apk.py diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py index 4788e198f..8740f9bd1 100644 --- a/src/briefcase/platforms/android/gradle.py +++ b/src/briefcase/platforms/android/gradle.py @@ -64,7 +64,7 @@ class GradleMixin: @property def packaging_formats(self): - return ["aab"] + return ["aab", "apk"] @property def default_packaging_format(self): diff --git a/tests/conftest.py b/tests/conftest.py index 4f91dcd4f..f64255ffc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,6 +90,7 @@ def first_app_config(): version="0.0.1", description="The first simple app", sources=["src/first"], + packaging_format="aab", ) diff --git a/tests/platforms/android/gradle/conftest.py b/tests/platforms/android/gradle/conftest.py index b3b212374..b9de86b07 100644 --- a/tests/platforms/android/gradle/conftest.py +++ b/tests/platforms/android/gradle/conftest.py @@ -1,8 +1,38 @@ +import os +import sys +from unittest.mock import MagicMock + import pytest +import requests + +from briefcase.console import Console, Log +from briefcase.integrations.android_sdk import AndroidSDK +from briefcase.integrations.subprocess import Subprocess +from briefcase.platforms.android.gradle import GradlePackageCommand from ....utils import create_file +@pytest.fixture +def package_command(tmp_path, first_app_config): + command = GradlePackageCommand( + logger=Log(), + console=Console(), + base_path=tmp_path / "base_path", + data_path=tmp_path / "briefcase", + ) + command.tools.android_sdk = MagicMock(spec_set=AndroidSDK) + command.tools.os = MagicMock(spec_set=os) + command.tools.os.environ = {} + command.tools.sys = MagicMock(spec_set=sys) + command.tools.requests = MagicMock(spec_set=requests) + command.tools.subprocess = MagicMock(spec_set=Subprocess) + + # Make sure the dist folder exists + (tmp_path / "base_path" / "dist").mkdir(parents=True) + return command + + @pytest.fixture def first_app_generated(first_app_config, tmp_path): # Create the briefcase.toml file diff --git a/tests/platforms/android/gradle/test_package.py b/tests/platforms/android/gradle/test_package.py index e89ea5c78..1d3069bb8 100644 --- a/tests/platforms/android/gradle/test_package.py +++ b/tests/platforms/android/gradle/test_package.py @@ -1,142 +1,2 @@ -import os -import sys -from subprocess import CalledProcessError -from unittest.mock import MagicMock - -import pytest -import requests - -from briefcase.console import Console, Log -from briefcase.exceptions import BriefcaseCommandError -from briefcase.integrations.android_sdk import AndroidSDK -from briefcase.integrations.subprocess import Subprocess -from briefcase.platforms.android.gradle import GradlePackageCommand - -from ....utils import create_file - - -@pytest.fixture -def package_command(tmp_path, first_app_config): - command = GradlePackageCommand( - logger=Log(), - console=Console(), - base_path=tmp_path / "base_path", - data_path=tmp_path / "briefcase", - ) - command.tools.android_sdk = MagicMock(spec_set=AndroidSDK) - command.tools.os = MagicMock(spec_set=os) - command.tools.os.environ = {} - command.tools.sys = MagicMock(spec_set=sys) - command.tools.requests = MagicMock(spec_set=requests) - command.tools.subprocess = MagicMock(spec_set=Subprocess) - - # Make sure the dist folder exists - (tmp_path / "base_path" / "dist").mkdir(parents=True) - return command - - -def test_unsupported_template_version(package_command, first_app_generated): - """Error raised if template's target version is not supported.""" - # Mock the build command previously called - create_file(package_command.binary_path(first_app_generated), content="") - - package_command.verify_app = MagicMock(wraps=package_command.verify_app) - - package_command._briefcase_toml.update( - {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}} - ) - - with pytest.raises( - BriefcaseCommandError, - match="The app template used to generate this app is not compatible", - ): - package_command(first_app_generated, packaging_format="aab") - - package_command.verify_app.assert_called_once_with(first_app_generated) - - def test_packaging_formats(package_command): - assert package_command.packaging_formats == ["aab"] - - -def test_default_packaging_format(package_command): - assert package_command.default_packaging_format == "aab" - - -def test_distribution_path(package_command, first_app_config, tmp_path): - assert ( - package_command.distribution_path(first_app_config) - == tmp_path / "base_path" / "dist" / "First App-0.0.1.aab" - ) - - -@pytest.mark.parametrize( - "host_os,gradlew_name", - [("Windows", "gradlew.bat"), ("NonWindows", "gradlew")], -) -def test_execute_gradle( - package_command, - first_app_config, - host_os, - gradlew_name, - tmp_path, -): - """Validate that package_app() will launch `gradlew bundleRelease` with the - appropriate environment & cwd, and that it will use `gradlew.bat` on Windows but - `gradlew` elsewhere.""" - # Mock out `host_os` so we can validate which name is used for gradlew. - package_command.tools.host_os = host_os - - # Set up a side effect of invoking gradle to create a bundle - def create_bundle(*args, **kwargs): - create_file( - tmp_path - / "base_path" - / "build" - / "first-app" - / "android" - / "gradle" - / "app" - / "build" - / "outputs" - / "bundle" - / "release" - / "app-release.aab", - "Android release", - ) - - package_command.tools.subprocess.run.side_effect = create_bundle - - # Create mock environment with `key`, which we expect to be preserved, and - # `ANDROID_SDK_ROOT`, which we expect to be overwritten. - package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} - - package_command.package_app(first_app_config) - - package_command.tools.android_sdk.verify_emulator.assert_called_once_with() - package_command.tools.subprocess.run.assert_called_once_with( - [ - package_command.bundle_path(first_app_config) / gradlew_name, - "bundleRelease", - "--console", - "plain", - ], - cwd=package_command.bundle_path(first_app_config), - env=package_command.tools.android_sdk.env, - check=True, - ) - - # The release asset has been moved into the dist folder - assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.aab").exists() - - -def test_print_gradle_errors(package_command, first_app_config): - """Validate that package_app() will convert stderr/stdout from the process into - exception text.""" - # Create a mock subprocess that crashes, printing text partly in non-ASCII. - package_command.tools.subprocess.run.side_effect = CalledProcessError( - returncode=1, - cmd=["ignored"], - ) - with pytest.raises(BriefcaseCommandError): - package_command.package_app(first_app_config) + assert package_command.packaging_formats == ["aab", "apk"] diff --git a/tests/platforms/android/gradle/test_package__aab.py b/tests/platforms/android/gradle/test_package__aab.py new file mode 100644 index 000000000..6a657bf70 --- /dev/null +++ b/tests/platforms/android/gradle/test_package__aab.py @@ -0,0 +1,111 @@ +from subprocess import CalledProcessError +from unittest.mock import MagicMock + +import pytest + +from briefcase.exceptions import BriefcaseCommandError + +from ....utils import create_file + + +def test_unsupported_template_version(package_command, first_app_generated): + """Error raised if template's target version is not supported.""" + # Mock the build command previously called + create_file(package_command.binary_path(first_app_generated), content="") + + package_command.verify_app = MagicMock(wraps=package_command.verify_app) + + package_command._briefcase_toml.update( + {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}} + ) + + with pytest.raises( + BriefcaseCommandError, + match="The app template used to generate this app is not compatible", + ): + package_command(first_app_generated, packaging_format="aab") + + package_command.verify_app.assert_called_once_with(first_app_generated) + + +def test_default_packaging_format(package_command): + assert package_command.default_packaging_format == "aab" + + +def test_distribution_path(package_command, first_app_config, tmp_path): + assert ( + package_command.distribution_path(first_app_config) + == tmp_path / "base_path" / "dist" / "First App-0.0.1.aab" + ) + + +@pytest.mark.parametrize( + "host_os,gradlew_name", + [("Windows", "gradlew.bat"), ("NonWindows", "gradlew")], +) +def test_execute_gradle( + package_command, + first_app_config, + host_os, + gradlew_name, + tmp_path, +): + """Validate that package_app() will launch `gradlew bundleRelease` with the + appropriate environment & cwd, and that it will use `gradlew.bat` on Windows but + `gradlew` elsewhere.""" + # Mock out `host_os` so we can validate which name is used for gradlew. + package_command.tools.host_os = host_os + + # Set up a side effect of invoking gradle to create a bundle + def create_bundle(*args, **kwargs): + create_file( + tmp_path + / "base_path" + / "build" + / "first-app" + / "android" + / "gradle" + / "app" + / "build" + / "outputs" + / "bundle" + / "release" + / "app-release.aab", + "Android release", + ) + + package_command.tools.subprocess.run.side_effect = create_bundle + + # Create mock environment with `key`, which we expect to be preserved, and + # `ANDROID_SDK_ROOT`, which we expect to be overwritten. + package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} + + package_command.package_app(first_app_config) + + package_command.tools.android_sdk.verify_emulator.assert_called_once_with() + package_command.tools.subprocess.run.assert_called_once_with( + [ + package_command.bundle_path(first_app_config) / gradlew_name, + "bundleRelease", + "--console", + "plain", + ], + cwd=package_command.bundle_path(first_app_config), + env=package_command.tools.android_sdk.env, + check=True, + ) + + # The release asset has been moved into the dist folder + assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.aab").exists() + + +def test_print_gradle_errors(package_command, first_app_config): + """Validate that package_app() will convert stderr/stdout from the process into + exception text.""" + # Create a mock subprocess that crashes, printing text partly in non-ASCII. + package_command.tools.subprocess.run.side_effect = CalledProcessError( + returncode=1, + cmd=["ignored"], + ) + with pytest.raises(BriefcaseCommandError): + package_command.package_app(first_app_config) diff --git a/tests/platforms/android/gradle/test_package__apk.py b/tests/platforms/android/gradle/test_package__apk.py new file mode 100644 index 000000000..6de02e79f --- /dev/null +++ b/tests/platforms/android/gradle/test_package__apk.py @@ -0,0 +1,135 @@ +import os +import sys +from subprocess import CalledProcessError +from unittest.mock import MagicMock + +import pytest +import requests + +from briefcase.console import Console, Log +from briefcase.exceptions import BriefcaseCommandError +from briefcase.integrations.android_sdk import AndroidSDK +from briefcase.integrations.subprocess import Subprocess +from briefcase.platforms.android.gradle import GradlePackageCommand + +from ....utils import create_file + + +@pytest.fixture +def package_command(tmp_path, first_app_config): + command = GradlePackageCommand( + logger=Log(), + console=Console(), + base_path=tmp_path / "base_path", + data_path=tmp_path / "briefcase", + ) + command.tools.android_sdk = MagicMock(spec_set=AndroidSDK) + command.tools.os = MagicMock(spec_set=os) + command.tools.os.environ = {} + command.tools.sys = MagicMock(spec_set=sys) + command.tools.requests = MagicMock(spec_set=requests) + command.tools.subprocess = MagicMock(spec_set=Subprocess) + + # Make sure the dist folder exists + (tmp_path / "base_path" / "dist").mkdir(parents=True) + return command + + +def test_unsupported_template_version(package_command, first_app_generated): + """Error raised if template's target version is not supported.""" + # Mock the build command previously called + create_file(package_command.binary_path(first_app_generated), content="") + + package_command.verify_app = MagicMock(wraps=package_command.verify_app) + + package_command._briefcase_toml.update( + {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}} + ) + + with pytest.raises( + BriefcaseCommandError, + match="The app template used to generate this app is not compatible", + ): + package_command(first_app_generated, packaging_format="apk") + + package_command.verify_app.assert_called_once_with(first_app_generated) + + +def test_distribution_path(package_command, first_app_config, tmp_path): + print(package_command.packaging_formats) + assert ( + package_command.distribution_path(first_app_config) + == tmp_path / "base_path" / "dist" / "First App-0.0.1.apk" + ) + + +@pytest.mark.parametrize( + "host_os,gradlew_name", + [("Windows", "gradlew.bat"), ("NonWindows", "gradlew")], +) +def test_execute_gradle( + package_command, + first_app_config, + host_os, + gradlew_name, + tmp_path, +): + """Validate that package_app() will launch `gradlew bundleRelease` with the + appropriate environment & cwd, and that it will use `gradlew.bat` on Windows but + `gradlew` elsewhere.""" + # Mock out `host_os` so we can validate which name is used for gradlew. + package_command.tools.host_os = host_os + + # Set up a side effect of invoking gradle to create a bundle + def create_bundle(*args, **kwargs): + create_file( + tmp_path + / "base_path" + / "build" + / "first-app" + / "android" + / "gradle" + / "app" + / "build" + / "outputs" + / "bundle" + / "release" + / "app-release.apk", + "Android release", + ) + + package_command.tools.subprocess.run.side_effect = create_bundle + + # Create mock environment with `key`, which we expect to be preserved, and + # `ANDROID_SDK_ROOT`, which we expect to be overwritten. + package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} + + package_command.package_app(first_app_config) + + package_command.tools.android_sdk.verify_emulator.assert_called_once_with() + package_command.tools.subprocess.run.assert_called_once_with( + [ + package_command.bundle_path(first_app_config) / gradlew_name, + "bundleRelease", + "--console", + "plain", + ], + cwd=package_command.bundle_path(first_app_config), + env=package_command.tools.android_sdk.env, + check=True, + ) + + # The release asset has been moved into the dist folder + assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.apk").exists() + + +def test_print_gradle_errors(package_command, first_app_config): + """Validate that package_app() will convert stderr/stdout from the process into + exception text.""" + # Create a mock subprocess that crashes, printing text partly in non-ASCII. + package_command.tools.subprocess.run.side_effect = CalledProcessError( + returncode=1, + cmd=["ignored"], + ) + with pytest.raises(BriefcaseCommandError): + package_command.package_app(first_app_config) From 257bda01e407dcafbe03f69a183ba5aac26dc5fb Mon Sep 17 00:00:00 2001 From: epba23 Date: Sat, 22 Jul 2023 16:53:22 +0200 Subject: [PATCH 02/10] split tests into aab and apk added packaging_format to GradleMixin.distribution_path --- src/briefcase/platforms/android/gradle.py | 4 +++- tests/conftest.py | 1 - .../android/gradle/test_package__aab.py | 18 ++++++++++----- .../android/gradle/test_package__apk.py | 22 ++++++++++++------- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py index 8740f9bd1..fd3a10051 100644 --- a/src/briefcase/platforms/android/gradle.py +++ b/src/briefcase/platforms/android/gradle.py @@ -88,7 +88,9 @@ def binary_path(self, app): ) def distribution_path(self, app): - return self.dist_path / f"{app.formal_name}-{app.version}.aab" + return ( + self.dist_path / f"{app.formal_name}-{app.version}.{app.packaging_format}" + ) def run_gradle(self, app, args): # Gradle may install the emulator via the dependency chain build-tools > tools > diff --git a/tests/conftest.py b/tests/conftest.py index f64255ffc..4f91dcd4f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,7 +90,6 @@ def first_app_config(): version="0.0.1", description="The first simple app", sources=["src/first"], - packaging_format="aab", ) diff --git a/tests/platforms/android/gradle/test_package__aab.py b/tests/platforms/android/gradle/test_package__aab.py index 6a657bf70..4879c8de1 100644 --- a/tests/platforms/android/gradle/test_package__aab.py +++ b/tests/platforms/android/gradle/test_package__aab.py @@ -8,6 +8,12 @@ from ....utils import create_file +@pytest.fixture +def first_app_aab(first_app_config): + first_app_config.packaging_format = "aab" + return first_app_config + + def test_unsupported_template_version(package_command, first_app_generated): """Error raised if template's target version is not supported.""" # Mock the build command previously called @@ -32,9 +38,9 @@ def test_default_packaging_format(package_command): assert package_command.default_packaging_format == "aab" -def test_distribution_path(package_command, first_app_config, tmp_path): +def test_distribution_path(package_command, first_app_aab, tmp_path): assert ( - package_command.distribution_path(first_app_config) + package_command.distribution_path(first_app_aab) == tmp_path / "base_path" / "dist" / "First App-0.0.1.aab" ) @@ -45,7 +51,7 @@ def test_distribution_path(package_command, first_app_config, tmp_path): ) def test_execute_gradle( package_command, - first_app_config, + first_app_aab, host_os, gradlew_name, tmp_path, @@ -80,17 +86,17 @@ def create_bundle(*args, **kwargs): # `ANDROID_SDK_ROOT`, which we expect to be overwritten. package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} - package_command.package_app(first_app_config) + package_command.package_app(first_app_aab) package_command.tools.android_sdk.verify_emulator.assert_called_once_with() package_command.tools.subprocess.run.assert_called_once_with( [ - package_command.bundle_path(first_app_config) / gradlew_name, + package_command.bundle_path(first_app_aab) / gradlew_name, "bundleRelease", "--console", "plain", ], - cwd=package_command.bundle_path(first_app_config), + cwd=package_command.bundle_path(first_app_aab), env=package_command.tools.android_sdk.env, check=True, ) diff --git a/tests/platforms/android/gradle/test_package__apk.py b/tests/platforms/android/gradle/test_package__apk.py index 6de02e79f..f738f5ff2 100644 --- a/tests/platforms/android/gradle/test_package__apk.py +++ b/tests/platforms/android/gradle/test_package__apk.py @@ -35,6 +35,12 @@ def package_command(tmp_path, first_app_config): return command +@pytest.fixture +def first_app_apk(first_app_config): + first_app_config.packaging_format = "apk" + return first_app_config + + def test_unsupported_template_version(package_command, first_app_generated): """Error raised if template's target version is not supported.""" # Mock the build command previously called @@ -55,10 +61,10 @@ def test_unsupported_template_version(package_command, first_app_generated): package_command.verify_app.assert_called_once_with(first_app_generated) -def test_distribution_path(package_command, first_app_config, tmp_path): +def test_distribution_path(package_command, first_app_apk, tmp_path): print(package_command.packaging_formats) assert ( - package_command.distribution_path(first_app_config) + package_command.distribution_path(first_app_apk) == tmp_path / "base_path" / "dist" / "First App-0.0.1.apk" ) @@ -69,7 +75,7 @@ def test_distribution_path(package_command, first_app_config, tmp_path): ) def test_execute_gradle( package_command, - first_app_config, + first_app_apk, host_os, gradlew_name, tmp_path, @@ -104,17 +110,17 @@ def create_bundle(*args, **kwargs): # `ANDROID_SDK_ROOT`, which we expect to be overwritten. package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} - package_command.package_app(first_app_config) + package_command.package_app(first_app_apk) package_command.tools.android_sdk.verify_emulator.assert_called_once_with() package_command.tools.subprocess.run.assert_called_once_with( [ - package_command.bundle_path(first_app_config) / gradlew_name, + package_command.bundle_path(first_app_apk) / gradlew_name, "bundleRelease", "--console", "plain", ], - cwd=package_command.bundle_path(first_app_config), + cwd=package_command.bundle_path(first_app_apk), env=package_command.tools.android_sdk.env, check=True, ) @@ -123,7 +129,7 @@ def create_bundle(*args, **kwargs): assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.apk").exists() -def test_print_gradle_errors(package_command, first_app_config): +def test_print_gradle_errors(package_command, first_app_apk): """Validate that package_app() will convert stderr/stdout from the process into exception text.""" # Create a mock subprocess that crashes, printing text partly in non-ASCII. @@ -132,4 +138,4 @@ def test_print_gradle_errors(package_command, first_app_config): cmd=["ignored"], ) with pytest.raises(BriefcaseCommandError): - package_command.package_app(first_app_config) + package_command.package_app(first_app_apk) From 69128a3b5b30fe5a56195d033fe9d29d8055820f Mon Sep 17 00:00:00 2001 From: epba23 Date: Sun, 23 Jul 2023 10:55:48 +0200 Subject: [PATCH 03/10] added new apk option --- src/briefcase/platforms/android/gradle.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py index fd3a10051..857d02eca 100644 --- a/src/briefcase/platforms/android/gradle.py +++ b/src/briefcase/platforms/android/gradle.py @@ -76,6 +76,22 @@ def __init__(self, *args, **kwargs): def project_path(self, app): return self.bundle_path(app) + def package_name(self, app) -> Path: + package_name_dict = { + "aab": Path("bundle") / "release" / "app-release.aab", + "apk": Path("apk") / "release" / "app-release-unsigned.apk", + "debug-apk": Path("apk") / "debug" / "app-debug.apk", + } + return package_name_dict[app.packaging_format] + + def build_command(self, app): + command_dict = { + "aab": "bundleRelease", + "apk": "assembleRelease", + "debug-apk": "assembleDebug", + } + return command_dict[app.packaging_format] + def binary_path(self, app): return ( self.bundle_path(app) @@ -374,7 +390,7 @@ def package_app(self, app: BaseConfig, **kwargs): ) with self.input.wait_bar("Bundling..."): try: - self.run_gradle(app, ["bundleRelease"]) + self.run_gradle(app, [self.build_command(app)]) except subprocess.CalledProcessError as e: raise BriefcaseCommandError("Error while building project.") from e @@ -384,9 +400,7 @@ def package_app(self, app: BaseConfig, **kwargs): / "app" / "build" / "outputs" - / "bundle" - / "release" - / "app-release.aab", + / f"{self.package_name(app)}", self.distribution_path(app), ) From 577e28c564d0152d3b925fe53a79760d4436fb62 Mon Sep 17 00:00:00 2001 From: epba23 Date: Sun, 23 Jul 2023 11:07:30 +0200 Subject: [PATCH 04/10] updated tests --- tests/platforms/android/gradle/test_package__aab.py | 4 ++-- tests/platforms/android/gradle/test_package__apk.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/platforms/android/gradle/test_package__aab.py b/tests/platforms/android/gradle/test_package__aab.py index 4879c8de1..34c104b96 100644 --- a/tests/platforms/android/gradle/test_package__aab.py +++ b/tests/platforms/android/gradle/test_package__aab.py @@ -105,7 +105,7 @@ def create_bundle(*args, **kwargs): assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.aab").exists() -def test_print_gradle_errors(package_command, first_app_config): +def test_print_gradle_errors(package_command, first_app_aab): """Validate that package_app() will convert stderr/stdout from the process into exception text.""" # Create a mock subprocess that crashes, printing text partly in non-ASCII. @@ -114,4 +114,4 @@ def test_print_gradle_errors(package_command, first_app_config): cmd=["ignored"], ) with pytest.raises(BriefcaseCommandError): - package_command.package_app(first_app_config) + package_command.package_app(first_app_aab) diff --git a/tests/platforms/android/gradle/test_package__apk.py b/tests/platforms/android/gradle/test_package__apk.py index f738f5ff2..fb7be0c46 100644 --- a/tests/platforms/android/gradle/test_package__apk.py +++ b/tests/platforms/android/gradle/test_package__apk.py @@ -98,9 +98,9 @@ def create_bundle(*args, **kwargs): / "app" / "build" / "outputs" - / "bundle" + / "apk" / "release" - / "app-release.apk", + / "app-release-unsigned.apk", "Android release", ) @@ -116,7 +116,7 @@ def create_bundle(*args, **kwargs): package_command.tools.subprocess.run.assert_called_once_with( [ package_command.bundle_path(first_app_apk) / gradlew_name, - "bundleRelease", + "assembleRelease", "--console", "plain", ], From 0fa75a9637587865c97fa05dfc07327e16168ff8 Mon Sep 17 00:00:00 2001 From: epba23 Date: Sun, 23 Jul 2023 11:11:54 +0200 Subject: [PATCH 05/10] added change file --- changes/1136.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1136.misc.rst diff --git a/changes/1136.misc.rst b/changes/1136.misc.rst new file mode 100644 index 000000000..d42988021 --- /dev/null +++ b/changes/1136.misc.rst @@ -0,0 +1 @@ +briefcase package android now has a -p apk option, which will (a) compile a release APK, and (b) copy that release APK to the dist folder. From c0055198f0d13b15baf677c018b069fec04d3984 Mon Sep 17 00:00:00 2001 From: epba23 Date: Sun, 23 Jul 2023 12:54:55 +0200 Subject: [PATCH 06/10] removed redundant fixtures, added apk-debug --- src/briefcase/platforms/android/gradle.py | 10 +- .../platforms/android/gradle/test_package.py | 2 +- .../android/gradle/test_package__apk.py | 27 ----- .../android/gradle/test_package_debugapk.py | 114 ++++++++++++++++++ 4 files changed, 123 insertions(+), 30 deletions(-) create mode 100644 tests/platforms/android/gradle/test_package_debugapk.py diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py index 857d02eca..471172c3a 100644 --- a/src/briefcase/platforms/android/gradle.py +++ b/src/briefcase/platforms/android/gradle.py @@ -64,7 +64,7 @@ class GradleMixin: @property def packaging_formats(self): - return ["aab", "apk"] + return ["aab", "apk", "debug-apk"] @property def default_packaging_format(self): @@ -104,8 +104,14 @@ def binary_path(self, app): ) def distribution_path(self, app): + packaging_format_extension = { + "aab": "aab", + "apk": "apk", + "debug-apk": "apk", + } return ( - self.dist_path / f"{app.formal_name}-{app.version}.{app.packaging_format}" + self.dist_path + / f"{app.formal_name}-{app.version}.{packaging_format_extension[app.packaging_format]}" ) def run_gradle(self, app, args): diff --git a/tests/platforms/android/gradle/test_package.py b/tests/platforms/android/gradle/test_package.py index 1d3069bb8..64179edfe 100644 --- a/tests/platforms/android/gradle/test_package.py +++ b/tests/platforms/android/gradle/test_package.py @@ -1,2 +1,2 @@ def test_packaging_formats(package_command): - assert package_command.packaging_formats == ["aab", "apk"] + assert package_command.packaging_formats == ["aab", "apk", "debug-apk"] diff --git a/tests/platforms/android/gradle/test_package__apk.py b/tests/platforms/android/gradle/test_package__apk.py index fb7be0c46..9614e267b 100644 --- a/tests/platforms/android/gradle/test_package__apk.py +++ b/tests/platforms/android/gradle/test_package__apk.py @@ -1,40 +1,13 @@ -import os -import sys from subprocess import CalledProcessError from unittest.mock import MagicMock import pytest -import requests -from briefcase.console import Console, Log from briefcase.exceptions import BriefcaseCommandError -from briefcase.integrations.android_sdk import AndroidSDK -from briefcase.integrations.subprocess import Subprocess -from briefcase.platforms.android.gradle import GradlePackageCommand from ....utils import create_file -@pytest.fixture -def package_command(tmp_path, first_app_config): - command = GradlePackageCommand( - logger=Log(), - console=Console(), - base_path=tmp_path / "base_path", - data_path=tmp_path / "briefcase", - ) - command.tools.android_sdk = MagicMock(spec_set=AndroidSDK) - command.tools.os = MagicMock(spec_set=os) - command.tools.os.environ = {} - command.tools.sys = MagicMock(spec_set=sys) - command.tools.requests = MagicMock(spec_set=requests) - command.tools.subprocess = MagicMock(spec_set=Subprocess) - - # Make sure the dist folder exists - (tmp_path / "base_path" / "dist").mkdir(parents=True) - return command - - @pytest.fixture def first_app_apk(first_app_config): first_app_config.packaging_format = "apk" diff --git a/tests/platforms/android/gradle/test_package_debugapk.py b/tests/platforms/android/gradle/test_package_debugapk.py new file mode 100644 index 000000000..aeb9de34b --- /dev/null +++ b/tests/platforms/android/gradle/test_package_debugapk.py @@ -0,0 +1,114 @@ +from subprocess import CalledProcessError +from unittest.mock import MagicMock + +import pytest + +from briefcase.exceptions import BriefcaseCommandError + +from ....utils import create_file + + +@pytest.fixture +def first_app_apk(first_app_config): + first_app_config.packaging_format = "debug-apk" + return first_app_config + + +def test_unsupported_template_version(package_command, first_app_generated): + """Error raised if template's target version is not supported.""" + # Mock the build command previously called + create_file(package_command.binary_path(first_app_generated), content="") + + package_command.verify_app = MagicMock(wraps=package_command.verify_app) + + package_command._briefcase_toml.update( + {first_app_generated: {"briefcase": {"target_epoch": "0.3.16"}}} + ) + + with pytest.raises( + BriefcaseCommandError, + match="The app template used to generate this app is not compatible", + ): + package_command(first_app_generated, packaging_format="debug-apk") + + package_command.verify_app.assert_called_once_with(first_app_generated) + + +def test_distribution_path(package_command, first_app_apk, tmp_path): + print(package_command.packaging_formats) + assert ( + package_command.distribution_path(first_app_apk) + == tmp_path / "base_path" / "dist" / "First App-0.0.1.apk" + ) + + +@pytest.mark.parametrize( + "host_os,gradlew_name", + [("Windows", "gradlew.bat"), ("NonWindows", "gradlew")], +) +def test_execute_gradle( + package_command, + first_app_apk, + host_os, + gradlew_name, + tmp_path, +): + """Validate that package_app() will launch `gradlew bundleRelease` with the + appropriate environment & cwd, and that it will use `gradlew.bat` on Windows but + `gradlew` elsewhere.""" + # Mock out `host_os` so we can validate which name is used for gradlew. + package_command.tools.host_os = host_os + + # Set up a side effect of invoking gradle to create a bundle + def create_bundle(*args, **kwargs): + create_file( + tmp_path + / "base_path" + / "build" + / "first-app" + / "android" + / "gradle" + / "app" + / "build" + / "outputs" + / "apk" + / "debug" + / "app-debug.apk", + "Android release", + ) + + package_command.tools.subprocess.run.side_effect = create_bundle + + # Create mock environment with `key`, which we expect to be preserved, and + # `ANDROID_SDK_ROOT`, which we expect to be overwritten. + package_command.tools.os.environ = {"ANDROID_SDK_ROOT": "somewhere", "key": "value"} + + package_command.package_app(first_app_apk) + + package_command.tools.android_sdk.verify_emulator.assert_called_once_with() + package_command.tools.subprocess.run.assert_called_once_with( + [ + package_command.bundle_path(first_app_apk) / gradlew_name, + "assembleDebug", + "--console", + "plain", + ], + cwd=package_command.bundle_path(first_app_apk), + env=package_command.tools.android_sdk.env, + check=True, + ) + + # The release asset has been moved into the dist folder + assert (tmp_path / "base_path" / "dist" / "First App-0.0.1.apk").exists() + + +def test_print_gradle_errors(package_command, first_app_apk): + """Validate that package_app() will convert stderr/stdout from the process into + exception text.""" + # Create a mock subprocess that crashes, printing text partly in non-ASCII. + package_command.tools.subprocess.run.side_effect = CalledProcessError( + returncode=1, + cmd=["ignored"], + ) + with pytest.raises(BriefcaseCommandError): + package_command.package_app(first_app_apk) From 161ab7f660895771bb88fdf0b168e27bd576f392 Mon Sep 17 00:00:00 2001 From: epba23 Date: Sun, 23 Jul 2023 12:57:19 +0200 Subject: [PATCH 07/10] moved default package test to test_package --- tests/platforms/android/gradle/test_package.py | 4 ++++ tests/platforms/android/gradle/test_package__aab.py | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/platforms/android/gradle/test_package.py b/tests/platforms/android/gradle/test_package.py index 64179edfe..0a039c7a2 100644 --- a/tests/platforms/android/gradle/test_package.py +++ b/tests/platforms/android/gradle/test_package.py @@ -1,2 +1,6 @@ def test_packaging_formats(package_command): assert package_command.packaging_formats == ["aab", "apk", "debug-apk"] + + +def test_default_packaging_format(package_command): + assert package_command.default_packaging_format == "aab" diff --git a/tests/platforms/android/gradle/test_package__aab.py b/tests/platforms/android/gradle/test_package__aab.py index 34c104b96..5cbee4231 100644 --- a/tests/platforms/android/gradle/test_package__aab.py +++ b/tests/platforms/android/gradle/test_package__aab.py @@ -34,10 +34,6 @@ def test_unsupported_template_version(package_command, first_app_generated): package_command.verify_app.assert_called_once_with(first_app_generated) -def test_default_packaging_format(package_command): - assert package_command.default_packaging_format == "aab" - - def test_distribution_path(package_command, first_app_aab, tmp_path): assert ( package_command.distribution_path(first_app_aab) From a996c98c145b47ab2cb1aadb0812e4259924b457 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 23 Jul 2023 12:57:42 +0200 Subject: [PATCH 08/10] Tweak release note. --- changes/1136.misc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/1136.misc.rst b/changes/1136.misc.rst index d42988021..082db4e98 100644 --- a/changes/1136.misc.rst +++ b/changes/1136.misc.rst @@ -1 +1 @@ -briefcase package android now has a -p apk option, which will (a) compile a release APK, and (b) copy that release APK to the dist folder. +Briefcase can now package Android APKs as a packaging artefact. From 1658b53320e58255e8ec2605615f17fe9ba57f0a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 23 Jul 2023 13:08:06 +0200 Subject: [PATCH 09/10] Rename debug APK test module. --- .../{test_package_debugapk.py => test_package__debug_apk.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/platforms/android/gradle/{test_package_debugapk.py => test_package__debug_apk.py} (100%) diff --git a/tests/platforms/android/gradle/test_package_debugapk.py b/tests/platforms/android/gradle/test_package__debug_apk.py similarity index 100% rename from tests/platforms/android/gradle/test_package_debugapk.py rename to tests/platforms/android/gradle/test_package__debug_apk.py From 06e188016c58537ce8e10cf78c0cf22884c9b896 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 23 Jul 2023 13:24:38 +0200 Subject: [PATCH 10/10] Added docs for the new Android packaging formats (and made the packaging docs consistent on other platforms) --- docs/reference/platforms/android.rst | 7 +++++++ docs/reference/platforms/macOS/app.rst | 11 ++++++++--- docs/reference/platforms/macOS/xcode.rst | 11 ++++++++--- docs/reference/platforms/windows/app.rst | 14 ++++++++++---- docs/reference/platforms/windows/visualstudio.rst | 11 ++++++++--- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/docs/reference/platforms/android.rst b/docs/reference/platforms/android.rst index 4123ddafd..17ac5259e 100644 --- a/docs/reference/platforms/android.rst +++ b/docs/reference/platforms/android.rst @@ -19,6 +19,13 @@ existing JDK install. If the above methods fail to find an Android SDK or Java JDK, Briefcase will download and install an isolated copy in its data directory. +Briefcase supports three packaging formats for an Android app: + +1. An AAB bundle (the default output of ``briefcase package android``, or by using + ``briefcase package android -p aab``); or +2. A Release APK (by using ``briefcase package android -p apk``); or +3. A Debug APK (by using ``briefcase package android -p debug-apk``). + Icon format =========== diff --git a/docs/reference/platforms/macOS/app.rst b/docs/reference/platforms/macOS/app.rst index 0fac08574..e547187ab 100644 --- a/docs/reference/platforms/macOS/app.rst +++ b/docs/reference/platforms/macOS/app.rst @@ -11,9 +11,14 @@ also be compressed to reduce their size for transport. By default, apps will be both signed and notarized when they are packaged. -The ``.app`` bundle is a distributable artefact. Alternatively, the ``.app`` -bundle can be packaged as a ``.dmg`` that contains the ``.app`` bundle. The -default packaging format is ``.dmg``. +Packaging format +================ + +Briefcase supports two packaging formats for a macOS ``.app`` bundle: + +1. A DMG that contains the ``.app`` bundle (the default output of ``briefcase package + macOS``, or by using ``briefcase package macOS -p dmg``); or +2. A zipped ``.app`` folder (using ``briefcase package macOS -p app``). Icon format =========== diff --git a/docs/reference/platforms/macOS/xcode.rst b/docs/reference/platforms/macOS/xcode.rst index 37a81c174..2820184e9 100644 --- a/docs/reference/platforms/macOS/xcode.rst +++ b/docs/reference/platforms/macOS/xcode.rst @@ -8,9 +8,14 @@ command or directly from Xcode. By default, apps will be both signed and notarized when they are packaged. -The Xcode project will produce a ``.app`` bundle is a distributable artefact. -Alternatively, this ``.app`` bundle can be packaged as a ``.dmg`` that contains -the ``.app`` bundle. The default packaging format is ``.dmg``. +Packaging format +================ + +Briefcase supports two packaging formats for a macOS Xcode project: + +1. A DMG that contains the ``.app`` bundle (the default output of ``briefcase package + macOS Xcode``, or by using ``briefcase package macOS Xcode -p dmg``); or +2. A zipped ``.app`` folder (using ``briefcase package macOS Xcode -p app``). Icon format =========== diff --git a/docs/reference/platforms/windows/app.rst b/docs/reference/platforms/windows/app.rst index 42e4b1ee0..c9696e2ad 100644 --- a/docs/reference/platforms/windows/app.rst +++ b/docs/reference/platforms/windows/app.rst @@ -3,11 +3,17 @@ Windows App =========== A Windows app is a stub binary, allow with a collection of folders that contain -the Python code for the app and the Python runtime libraries. Briefcase supports -two packaging formats for a Windows app: +the Python code for the app and the Python runtime libraries. -1. As an MSI installer -2. As a ZIP file containing all files needed to run the app +Packaging format +================ + +Briefcase supports two packaging formats for a Windows app: + +1. As an MSI installer (the default output of ``briefcase package windows``, or by using + ``briefcase package windows -p msi``); or +2. As a ZIP file containing all files needed to run the app (by using ``briefcase + package windows -p zip``). Briefcase uses the `WiX Toolset `__ to build an MSI installer for a Windows App. WiX, in turn, requires that .NET Framework 3.5 is diff --git a/docs/reference/platforms/windows/visualstudio.rst b/docs/reference/platforms/windows/visualstudio.rst index a760884e7..ae1d46092 100644 --- a/docs/reference/platforms/windows/visualstudio.rst +++ b/docs/reference/platforms/windows/visualstudio.rst @@ -28,10 +28,15 @@ ensure that you have installed the following: - All default packages - C++/CLI support for v143 build tools -Briefcase supports two packaging formats for a Windows App: +Packaging format +================ -1. As an MSI installer -2. As a ZIP file containing all files needed to run the app +Briefcase supports two packaging formats for a Windows app: + +1. As an MSI installer (the default output of ``briefcase package windows + VisualStudio``, or by using ``briefcase package windows VisualStudio -p msi``); or +2. As a ZIP file containing all files needed to run the app (by using ``briefcase + package windows VisualStudio -p zip``). Briefcase uses the `WiX Toolset `__ to build an MSI installer for a Windows App. WiX, in turn, requires that .NET Framework 3.5 is