Skip to content

Commit

Permalink
Merge pull request beeware#1007 from mhsmith/rosetta
Browse files Browse the repository at this point in the history
Install Rosetta if necessary
  • Loading branch information
mhsmith authored Dec 16, 2022
2 parents 26991bf + 25e497c commit f771b3f
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 120 deletions.
1 change: 1 addition & 0 deletions changes/1000.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On macOS, Rosetta is now installed automatically if needed.
25 changes: 25 additions & 0 deletions src/briefcase/integrations/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ def verify(cls, tools: ToolCache, install=True):
java_home = tools.os.environ.get("JAVA_HOME", "")
install_message = None

if tools.host_arch == "arm64" and tools.host_os == "Darwin":
# Java 8 is not available for macOS on ARM64, so we will require Rosetta.
cls.verify_rosetta(tools)

# macOS has a helpful system utility to determine JAVA_HOME. Try it.
if not java_home and tools.host_os == "Darwin":
try:
Expand Down Expand Up @@ -284,3 +288,24 @@ def upgrade(self):

self.uninstall()
self.install()

@classmethod
def verify_rosetta(cls, tools):
try:
tools.subprocess.check_output(
["arch", "-x86_64", "true"], stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError:
tools.logger.info(
"""\
This command requires Rosetta, but it does not appear to be installed. Briefcase will
attempt to install it now.
"""
)
try:
tools.subprocess.run(
["softwareupdate", "--install-rosetta", "--agree-to-license"],
check=True,
)
except subprocess.CalledProcessError as e:
raise BriefcaseCommandError("Failed to install Rosetta") from e
49 changes: 25 additions & 24 deletions src/briefcase/platforms/android/gradle.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,30 @@ def distribution_path(self, app, packaging_format):
/ "app-release.aab"
)

def gradlew_path(self, app):
def run_gradle(self, app, args):
# Gradle may install the emulator via the dependency chain build-tools > tools >
# emulator. (The `tools` package only shows up in sdkmanager if you pass
# `--include_obsolete`.) However, the old sdkmanager built into Android Gradle
# plugin 4.2 doesn't know about macOS on ARM, so it'll install an x86_64 emulator
# which won't work with ARM system images.
#
# Work around this by pre-installing the emulator with our own sdkmanager before
# running Gradle. For simplicity, we do this on all platforms, since the user will
# almost certainly want an emulator soon enough.
self.tools.android_sdk.verify_emulator()

gradlew = "gradlew.bat" if self.tools.host_os == "Windows" else "gradlew"
return self.bundle_path(app) / gradlew
self.tools.subprocess.run(
# Windows needs the full path to `gradlew`; macOS & Linux can find it
# via `./gradlew`. For simplicity of implementation, we always provide
# the full path.
[self.bundle_path(app) / gradlew] + args + ["--console", "plain"],
env=self.tools.android_sdk.env,
# Set working directory so gradle can use the app bundle path as its
# project root, i.e., to avoid 'Task assembleDebug not found'.
cwd=self.bundle_path(app),
check=True,
)

def verify_tools(self):
"""Verify that the Android APK tools in `briefcase` will operate on
Expand Down Expand Up @@ -201,17 +222,7 @@ def build_app(self, app: BaseConfig, test_mode: bool, **kwargs):
self.logger.info("Building Android APK...", prefix=app.app_name)
with self.input.wait_bar("Building..."):
try:
self.tools.subprocess.run(
# Windows needs the full path to `gradlew`; macOS & Linux can find it
# via `./gradlew`. For simplicity of implementation, we always provide
# the full path.
[self.gradlew_path(app), "assembleDebug", "--console", "plain"],
env=self.tools.android_sdk.env,
# Set working directory so gradle can use the app bundle path as its
# project root, i.e., to avoid 'Task assembleDebug not found'.
cwd=self.bundle_path(app),
check=True,
)
self.run_gradle(app, ["assembleDebug"])
except subprocess.CalledProcessError as e:
raise BriefcaseCommandError("Error while building project.") from e

Expand Down Expand Up @@ -389,17 +400,7 @@ def package_app(self, app: BaseConfig, **kwargs):
)
with self.input.wait_bar("Bundling..."):
try:
self.tools.subprocess.run(
# Windows needs the full path to `gradlew`; macOS & Linux can find it
# via `./gradlew`. For simplicity of implementation, we always provide
# the full path.
[self.gradlew_path(app), "bundleRelease", "--console", "plain"],
env=self.tools.android_sdk.env,
# Set working directory so gradle can use the app bundle path as its
# project root, i.e., to avoid 'Task bundleRelease not found'.
cwd=self.bundle_path(app),
check=True,
)
self.run_gradle(app, ["bundleRelease"])
except subprocess.CalledProcessError as e:
raise BriefcaseCommandError("Error while building project.") from e

Expand Down
73 changes: 0 additions & 73 deletions tests/integrations/android_sdk/AndroidSDK/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,45 +99,6 @@ def test_succeeds_immediately_in_happy_path(mock_tools, tmp_path):
assert sdk.root_path == android_sdk_root_path


def test_succeeds_immediately_in_happy_path_with_debug(mock_tools, tmp_path):
"""If debug is enabled, a verify call will display the installed
packages."""
# Increase the log level.
mock_tools.logger.verbosity = 2

# If `sdkmanager` exists and has the right permissions, and
# `android-sdk-license` exists, verify() should
# succeed, create no subprocesses, make no requests, and return a
# SDK wrapper.

# On Windows, this requires `sdkmanager.bat`; on non-Windows, it requires
# `sdkmanager`.

# Create `sdkmanager` and the license file.
android_sdk_root_path = tmp_path / "tools" / "android_sdk"
tools_bin = android_sdk_root_path / "cmdline-tools" / "latest" / "bin"
tools_bin.mkdir(parents=True, mode=0o755)
if platform.system() == "Windows":
sdk_manager = tools_bin / "sdkmanager.bat"
sdk_manager.touch()
else:
sdk_manager = tools_bin / "sdkmanager"
sdk_manager.touch(mode=0o755)

# Pre-accept the license
accept_license(android_sdk_root_path)()

# Expect verify() to succeed
sdk = AndroidSDK.verify(mock_tools)

# No calls to download or unpack anything.
mock_tools.download.file.assert_not_called()
mock_tools.shutil.unpack_archive.assert_not_called()

# The returned SDK has the expected root path.
assert sdk.root_path == android_sdk_root_path


def test_user_provided_sdk(mock_tools, tmp_path):
"""If the user specifies a valid ANDROID_SDK_ROOT, it is used."""
# Increase the log level.
Expand Down Expand Up @@ -173,40 +134,6 @@ def test_user_provided_sdk(mock_tools, tmp_path):
assert sdk.root_path == existing_android_sdk_root_path


def test_user_provided_sdk_with_debug(mock_tools, tmp_path):
"""If the has debug with a user-specified ANDROID_SDK_ROOT, the packages
are listed."""
# Create `sdkmanager` and the license file.
existing_android_sdk_root_path = tmp_path / "other_sdk"
tools_bin = existing_android_sdk_root_path / "cmdline-tools" / "latest" / "bin"
tools_bin.mkdir(parents=True, mode=0o755)
if platform.system() == "Windows":
sdk_manager = tools_bin / "sdkmanager.bat"
sdk_manager.touch()
else:
sdk_manager = tools_bin / "sdkmanager"
sdk_manager.touch(mode=0o755)

# Pre-accept the license
accept_license(existing_android_sdk_root_path)()

# Set the environment to specify ANDROID_SDK_ROOT
mock_tools.os.environ = {
"ANDROID_SDK_ROOT": os.fsdecode(existing_android_sdk_root_path)
}

# Expect verify() to succeed
sdk = AndroidSDK.verify(mock_tools)

# No calls to download, run or unpack anything.
mock_tools.download.file.assert_not_called()
mock_tools.subprocess.run.assert_not_called()
mock_tools.shutil.unpack_archive.assert_not_called()

# The returned SDK has the expected root path.
assert sdk.root_path == existing_android_sdk_root_path


def test_invalid_user_provided_sdk(mock_tools, tmp_path):
"""If the user specifies an invalid ANDROID_SDK_ROOT, it is ignored."""

Expand Down
Loading

0 comments on commit f771b3f

Please sign in to comment.