Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump Android Command-line Tools from 6.0 to 9.0 #1397

Merged
merged 4 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/1397.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Android SDK's Command-Line Tools is now version 9.0. If an externally-managed Android SDK is being used, it must provide this version of Command-Line Tools. Use the SDK Manager in Android Studio to ensure it is installed.
4 changes: 3 additions & 1 deletion docs/reference/platforms/android.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Gradle requires an install of the Android SDK and a Java 17 JDK.
If you have an existing install of the Android SDK, it will be used by Briefcase
if the ``ANDROID_HOME`` environment variable is set. If ``ANDROID_HOME`` is not
present in the environment, Briefcase will honor the deprecated
``ANDROID_SDK_ROOT`` environment variable.
``ANDROID_SDK_ROOT`` environment variable. Additionally, an existing SDK install
must have version 9.0 of Command-line Tools installed; this version can be
installed in the SDK Manager in Android Studio.

If you have an existing install of a Java 17 JDK, it will be used by Briefcase
if the ``JAVA_HOME`` environment variable is set. On macOS, if ``JAVA_HOME`` is
Expand Down
245 changes: 163 additions & 82 deletions src/briefcase/integrations/android_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class AndroidSDK(ManagedTool):
name = "android_sdk"
full_name = "Android SDK"

# Latest version for Command-Line Tools download as of August 2023
freakboy3742 marked this conversation as resolved.
Show resolved Hide resolved
SDK_MANAGER_DOWNLOAD_VER = "9477386"
SDK_MANAGER_VER = "9.0"

def __init__(self, tools: ToolCache, root_path: Path):
super().__init__(tools=tools)
self.dot_android_path = self.tools.home_path / ".android"
Expand All @@ -57,39 +61,30 @@ def __init__(self, tools: ToolCache, root_path: Path):

@property
def cmdline_tools_url(self) -> str:
"""The Android SDK Command-Line Tools URL appropriate to the current operating
system."""
"""The Android SDK Command-Line Tools URL appropriate to the current OS."""
platform_name = self.tools.host_os.lower()
if self.tools.host_os.lower() == "darwin":
platform_name = "mac"
elif self.tools.host_os.lower() == "windows": # pragma: no branch
platform_name = "win"

return f"https://dl.google.com/android/repository/commandlinetools-{platform_name}-{self.cmdline_tools_version}_latest.zip" # noqa: E501
return (
f"https://dl.google.com/android/repository/"
f"commandlinetools-{platform_name}-{self.SDK_MANAGER_DOWNLOAD_VER}_latest.zip"
)

@property
def cmdline_tools_path(self) -> Path:
return self.root_path / "cmdline-tools" / "latest"

@property
def cmdline_tools_version(self) -> str:
# This is the version of the Android SDK Command-line tools that
# are current as of May 2022. These tools can generally self-update,
# so using a fixed download URL isn't a problem.
# However, if/when this version number is changed, ensure that the
# checks done during verification include any required upgrade steps.
return "8092744"
"""Version-specific Command-line tools install root directory."""
return self.root_path / "cmdline-tools" / self.SDK_MANAGER_VER

@property
def cmdline_tools_version_path(self) -> Path:
return self.root_path / "cmdline-tools" / self.cmdline_tools_version
def sdkmanager_filename(self) -> str:
return "sdkmanager.bat" if self.tools.host_os == "Windows" else "sdkmanager"

@property
def sdkmanager_path(self) -> Path:
sdkmanager = (
"sdkmanager.bat" if self.tools.host_os == "Windows" else "sdkmanager"
)
return self.cmdline_tools_path / "bin" / sdkmanager
return self.cmdline_tools_path / "bin" / self.sdkmanager_filename

@property
def adb_path(self) -> Path:
Expand Down Expand Up @@ -226,15 +221,16 @@ def verify_install(
JDK.verify(tools=tools, install=install)

sdk = None
sdk_root, sdk_env_source = cls.sdk_path_from_env(tools=tools)

if sdk_root:
# Verify externally-managed Android SDK
sdk_root_env, sdk_source_env = cls.sdk_path_from_env(tools=tools)
if sdk_root_env:
tools.logger.debug("Evaluating ANDROID_HOME...", prefix=cls.full_name)
tools.logger.debug(f"{sdk_env_source}={sdk_root}")
sdk = AndroidSDK(tools=tools, root_path=Path(sdk_root))
tools.logger.debug(f"{sdk_source_env}={sdk_root_env}")
sdk = AndroidSDK(tools=tools, root_path=Path(sdk_root_env))

if sdk.exists():
if sdk_env_source == "ANDROID_SDK_ROOT":
if sdk_source_env == "ANDROID_SDK_ROOT":
tools.logger.warning(
"""
*************************************************************************
Expand All @@ -253,26 +249,50 @@ def verify_install(
*************************************************************************
"""
)
sdk.verify_license()
elif sdk.cmdline_tools_path.parent.exists():
# a cmdline-tools directory exists but the required version isn't installed.
# try to install the required version using the 'latest' version.
if not sdk.install_cmdline_tools():
sdk = None
tools.logger.warning(
f"""
*************************************************************************
** WARNING: Incompatible Command-Line Tools Version **
*************************************************************************

The Android SDK specified by {sdk_source_env} at:

{sdk_root_env}

does not contain Command-Line Tools version {cls.SDK_MANAGER_VER}. Briefcase requires
this version to be installed to use an external Android SDK.

Use Android Studio's SDK Manager to install Command-Line Tools {cls.SDK_MANAGER_VER}.

Briefcase will proceed using its own SDK instance.

*************************************************************************
"""
)
else:
sdk = None
tools.logger.warning(
f"""
*************************************************************************
** {f"WARNING: {sdk_env_source} does not point to an Android SDK":67} **
** {f"WARNING: {sdk_source_env} does not point to an Android SDK":67} **
*************************************************************************

The location pointed to by the {sdk_env_source} environment
The location pointed to by the {sdk_source_env} environment
variable:

{sdk_root}
{sdk_root_env}

doesn't appear to contain an Android SDK.

If {sdk_env_source} is an Android SDK, ensure it is the root directory
If {sdk_source_env} is an Android SDK, ensure it is the root directory
of the Android SDK instance such that

${sdk_env_source}/cmdline-tools/latest/bin/sdkmanager
${sdk_source_env}/cmdline-tools/{cls.SDK_MANAGER_VER}/bin/sdkmanager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit - this instruction won't be right on Windows, because of the .bat extension. Might as well clean this up while we're in the area.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; updated to use the existing methods to get the path for sdkmanager.


is a valid filepath.

Expand All @@ -282,52 +302,32 @@ def verify_install(
"""
)

# Verify Briefcase-managed Android SDK
if sdk is None:
# Build an SDK wrapper for the Briefcase SDK instance.
sdk_root_path = tools.base_path / "android_sdk"
sdk = AndroidSDK(tools=tools, root_path=sdk_root_path)

if sdk.exists():
# NOTE: For now, all known versions of the cmdline-tools are compatible.
# If/when that ever changes, do a verification check here.
sdk.verify_license()
else:
# The legacy SDK Tools exist. Delete them.
if (sdk_root_path / "tools").exists():
tools.logger.warning(
f"""
*************************************************************************
** WARNING: Upgrading Android SDK tools **
*************************************************************************

Briefcase needs to replace the older Android SDK Tools with the
newer Android SDK Command-Line Tools. This will involve some large
downloads, as well as re-accepting the licenses for the Android
SDKs.
if not sdk.exists():
if not install:
raise MissingToolError("Android SDK")

Any emulators created with the older Android SDK Tools will not be
compatible with the new tools. You will need to create new
emulators. Old emulators can be removed by deleting the files
in {sdk.avd_path} matching the emulator name.
sdk.delete_legacy_sdk_tools()

*************************************************************************
"""
)
tools.shutil.rmtree(sdk_root_path)
tools.logger.info(
"Upgrading Android SDK..."
if sdk.cmdline_tools_path.parent.exists()
else "The Android SDK was not found; downloading and installing...",
prefix=cls.name,
)
tools.logger.info(
"To use an existing Android SDK instance, specify its root "
"directory path in the ANDROID_HOME environment variable."
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, if someone already has a working Briefcase install and is upgrading, there's no need to let them know that they can use an existing Android SDK instance. I think we can bury the "to use an existing" warning unless we're doing a first install.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Details about ANDROID_HOME are only shown now for a new install.

tools.logger.info()
sdk.install()

if install:
tools.logger.info(
"The Android SDK was not found; downloading and installing...",
prefix=cls.name,
)
tools.logger.info(
"To use an existing Android SDK instance, "
"specify its root directory path in the ANDROID_HOME environment variable."
)
tools.logger.info()
sdk.install()
else:
raise MissingToolError("Android SDK")
# Licences must be accepted to use the SDK
sdk.verify_license()

tools.logger.debug(f"Using Android SDK at {sdk.root_path}")
tools.android_sdk = sdk
Expand All @@ -353,7 +353,6 @@ def managed_install(self) -> bool:

def uninstall(self):
"""The Android SDK is upgraded in-place instead of being reinstalled."""
pass

def install(self):
"""Download and install the Android SDK."""
Expand All @@ -364,18 +363,18 @@ def install(self):
)

# The cmdline-tools package *must* be installed as:
# <sdk_path>/cmdline-tools/latest
# <sdk_path>/cmdline-tools/<cmdline-tools version>
#
# However, the zip file unpacks a top-level folder named `cmdline-tools`.
# So, the unpacking process is:
#
# 1. Make a <sdk_path>/cmdline-tools folder
# 2. Unpack the zip file into that folder, creating <sdk_path>/cmdline-tools/cmdline-tools
# 3. Move <sdk_path>/cmdline-tools/cmdline-tools to <sdk_path>/cmdline-tools/latest
# 4. Drop a marker file named <sdk_path>/cmdline-tools/<version> so we can track
# the version that was installed.
# 3. Move <sdk_path>/cmdline-tools/cmdline-tools to <sdk_path>/cmdline-tools/<cmdline-tools version>

with self.tools.input.wait_bar("Installing Android SDK Command-Line Tools..."):
with self.tools.input.wait_bar(
f"Installing Android SDK Command-Line Tools {self.SDK_MANAGER_VER}..."
):
self.cmdline_tools_path.parent.mkdir(parents=True, exist_ok=True)
try:
self.tools.shutil.unpack_archive(
Expand All @@ -391,18 +390,14 @@ def install(self):
"""
) from e

# If there's an existing version of the cmdline tools (or the version marker), delete them.
# If there's an existing version of the cmdline tools, delete them.
if self.cmdline_tools_path.exists():
self.tools.shutil.rmtree(self.cmdline_tools_path)
if self.cmdline_tools_version_path.exists():
self.tools.os.unlink(self.cmdline_tools_version_path)

# Rename the top level zip content to the final name
(self.cmdline_tools_path.parent / "cmdline-tools").rename(
self.cmdline_tools_path
)
# Touch a file with the version that was installed.
self.cmdline_tools_version_path.touch()

# Zip file no longer needed once unpacked.
cmdline_tools_zip_path.unlink()
Expand All @@ -416,8 +411,8 @@ def install(self):
if not self.tools.os.access(binpath, self.tools.os.X_OK):
binpath.chmod(0o755)

# Licences must be accepted.
self.verify_license()
with self.tools.input.wait_bar("Removing Older Android SDK Packages..."):
self.cleanup_old_installs()

def upgrade(self):
"""Upgrade the Android SDK."""
Expand All @@ -440,6 +435,92 @@ def upgrade(self):
"""
) from e

def install_cmdline_tools(self) -> bool:
"""Attempt to use 'latest' cmdline-tools to install the currently required
version of the Command-Line Tools.

The Briefcase-managed SDK should always have the required version of cmdline-
tools installed; however, user-provided SDKs may not have it.

:returns: True if successfully installed; False otherwise
"""
self.tools.logger.info(
f"Installing Android Command-Line Tools {self.SDK_MANAGER_VER}...",
prefix=self.full_name,
)
self.tools.logger.info(f"Using Android SDK at {self.root_path}")
latest_sdkmanager_path = (
self.root_path
/ "cmdline-tools"
/ "latest"
/ "bin"
/ self.sdkmanager_filename
)
try:
self.tools.subprocess.run(
[
latest_sdkmanager_path,
f"cmdline-tools;{self.SDK_MANAGER_VER}",
],
check=True,
stream_output=False,
)
except (OSError, subprocess.CalledProcessError) as e:
self.tools.logger.debug(str(e))
self.tools.logger.warning(
f"Failed to install cmdline-tools;{self.SDK_MANAGER_VER}"
)
return False
return True

def delete_legacy_sdk_tools(self):
"""Delete any legacy Android SDK tools that are installed.

If no versions of the Command-Line Tools are installed but the 'tools' directory
exists, the legacy SDK Tools are probably installed. Since they have been
deprecated by more recent releases of SDK Manager, delete them and perform a
fresh install.

The Android SDK Tools were deprecated in Sept 2017.
freakboy3742 marked this conversation as resolved.
Show resolved Hide resolved
"""
if (
not self.cmdline_tools_path.parent.exists()
and (self.root_path / "tools").exists()
):
self.tools.logger.warning(
f"""
*************************************************************************
** WARNING: Upgrading Android SDK tools **
*************************************************************************

Briefcase needs to replace the older Android SDK Tools with the
newer Android SDK Command-Line Tools. This will involve some large
downloads, as well as re-accepting the licenses for the Android
SDKs.

Any emulators created with the older Android SDK Tools will not be
compatible with the new tools. You will need to create new
emulators. Old emulators can be removed by deleting the files
in {self.avd_path} matching the emulator name.

*************************************************************************
"""
)
self.tools.shutil.rmtree(self.root_path)

def cleanup_old_installs(self):
"""Remove old versions of Android SDK packages and version markers.

When the Android SDK is upgraded, old versions of packages should be removed to
keep the SDK tidy. This is namely the Command-line Tools that are used to manage
the SDK and AVDs. Additionally, previous version of Briefcase created a version
marker file that needs to be deleted.
"""
if (ver_file := self.cmdline_tools_path.parent / "8092744").is_file():
self.tools.os.unlink(ver_file)
if (latest := self.cmdline_tools_path.parent / "latest").is_dir():
self.tools.shutil.rmtree(latest)

def list_packages(self):
"""In debug output, list the packages currently managed by the SDK."""
try:
Expand Down
Loading
Loading