Repository: beeware/briefcase
Pull request #1778 (2024-05-06 17:56:49)

Bump Android SDK Commandline Tools from 9.0 to 12.0
## Changes
-  Requires Android Commandline Tools 12.0
- Whether using Briefcase's SDK install or a user-managed one, version 12.0 will just be installed if it isn't already

## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [x] All new features have been tested
- [x] All new features have been documented
- [X] I have read the **CONTRIBUTING.md** file
- [X] I will abide by the code of conduct

rmartin16: well....ever keeping us on our toes....Google's AVD manager in `cmdline-tools v12.0` apparently respects `XDG_CONFIG_HOME`. So, the AVD config is in `~/.config/.android/avd` instead of `~/.android/avd` now....and Briefcase can't find the AVD's config to update it...
rmartin16: FWIW, Android Studio will also respect `XDG_CONFIG_HOME` when creating an AVD.

However, `$BRIEFCASE_HOME/tools/android_sdk/emulator/emulator` will not be able to find this AVD that was just created in `$XDG_CONFIG_HOME/.android`.....because (surprise surprise) it doesn't respect `XDG_CONFIG_HOME`.

The `avdmanager` does [support](https://developer.android.com/tools/avdmanager) a `-p/--path` to define "the location where the directory for this AVD's files will be created".....but it still puts the AVD's `ini` file in `$XDG_CONFIG_HOME/.android` (!!!!!!) and we still have the same issue.

So, anyway....I think it'll be best to ensure `XDG_CONFIG_HOME` is always overridden when running `avdmanager`...

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1866b45b..3a04243f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -60,7 +60,7 @@ jobs:
         with:
           name: ${{ env.VERSION }}
           draft: true
-          artifacts: dist/${{ github.event.repository.name }}*
+          artifacts: dist/*
           artifactErrorsFailBuild: true
 
       - name: Publish release to Test PyPI
diff --git a/changes/1777.misc.rst b/changes/1777.misc.rst
deleted file mode 100644
index 60db71a5..00000000
--- a/changes/1777.misc.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-The release workflow was modified to exclude non-releasable artefacts.
diff --git a/changes/1778.feature.rst b/changes/1778.feature.rst
new file mode 100644
index 00000000..b77b569b
--- /dev/null
+++ b/changes/1778.feature.rst
@@ -0,0 +1,1 @@
+Android SDK Commandline Tools 12.0 is now used to build Android apps.
diff --git a/changes/1784.doc.rst b/changes/1784.doc.rst
deleted file mode 100644
index 0e5e3ddf..00000000
--- a/changes/1784.doc.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Resolved a typo in the Android reference documentation.
diff --git a/docs/reference/platforms/android/gradle.rst b/docs/reference/platforms/android/gradle.rst
index 430434fd..d610664b 100644
--- a/docs/reference/platforms/android/gradle.rst
+++ b/docs/reference/platforms/android/gradle.rst
@@ -37,7 +37,7 @@ 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. Additionally, an existing SDK install
-must have version 9.0 of Command-line Tools installed; this version can be
+must have version 12.0 of Command-line Tools installed; this version can be
 installed in the SDK Manager in Android Studio.
 
 Packaging format
@@ -132,9 +132,9 @@ The device or emulator to target. Can be specified as:
 * a JSON dictionary specifying the properties of a device that will be created.
   This dictionary must have, at a minimum, an AVD name:
 
-  .. code-block:: console
+.. code-block:: console
 
-       $ briefcase run -d '{"avd":"new-device"}'
+     $ briefcase run -d '{"avd":"new-device"}'
 
   You may also specify:
 
diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py
index 40802db9..ad3d2d68 100644
--- a/src/briefcase/integrations/android_sdk.py
+++ b/src/briefcase/integrations/android_sdk.py
@@ -48,10 +48,10 @@ class AndroidSDK(ManagedTool):
     name = "android_sdk"
     full_name = "Android SDK"
 
-    # Latest version for Command-Line Tools download as of August 2023
-    # **Be sure the android.rst docs stay in sync with version updates here**
-    SDK_MANAGER_DOWNLOAD_VER = "9477386"
-    SDK_MANAGER_VER = "9.0"
+    # Latest version for Command-Line Tools download as of May 2024
+    # **Be sure the gradle.rst docs stay in sync with version updates here**
+    SDK_MANAGER_DOWNLOAD_VER = "11076708"
+    SDK_MANAGER_VER = "12.0"
 
     def __init__(self, tools: ToolCache, root_path: Path):
         super().__init__(tools=tools)
@@ -1204,7 +1204,13 @@ In future, you can specify this device by running:
                         "--device",
                         device_type,
                     ],
-                    env=self.env,
+                    # Ensure XDG_CONFIG_HOME is not set so avdmanager uses the default
+                    # location (i.e. ~/.android) because the emulator does not respect
+                    # XDG_CONFIG_HOME and will not be able to find the AVD to run it.
+                    env={
+                        **self.env,
+                        **{"XDG_CONFIG_HOME": None},
+                    },
                 )
             except subprocess.CalledProcessError as e:
                 raise BriefcaseCommandError("Unable to create Android emulator") from e
diff --git a/src/briefcase/integrations/subprocess.py b/src/briefcase/integrations/subprocess.py
index 33ddb392..31c751db 100644
--- a/src/briefcase/integrations/subprocess.py
+++ b/src/briefcase/integrations/subprocess.py
@@ -302,15 +302,19 @@ class Subprocess(Tool):
         # This is a no-op; the native subprocess environment is ready-to-use.
         yield subprocess_kwargs
 
-    def full_env(self, overrides: dict[str, str]) -> dict[str, str]:
+    def full_env(self, overrides: dict[str, str | None] | None) -> dict[str, str]:
         """Generate the full environment in which the command will run.
 
+        If an env var in `overrides` is set to `None`, then that env var
+        will be altogether absent in the returned environment.
+
         :param overrides: The environment passed to the subprocess call;
             can be `None` if there are no explicit environment changes.
         """
         env = self.tools.os.environ.copy()
         if overrides:
             env.update(overrides)
+            env = {k: v for k, v in env.items() if v is not None}
         return env
 
     def final_kwargs(self, **kwargs) -> dict[str, str]:


Test patch:
diff --git a/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py b/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
index 9125dda6..35ada469 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test__create_emulator.py
@@ -100,7 +100,10 @@ def test_create_emulator(
             "--device",
             "slab",
         ],
-        env=android_sdk.env,
+        env={
+            **android_sdk.env,
+            **{"XDG_CONFIG_HOME": None},
+        },
     )
 
     # Emulator configuration file has been appended.
@@ -173,7 +176,10 @@ def test_create_emulator_with_defaults(
             "--device",
             "pixel",
         ],
-        env=android_sdk.env,
+        env={
+            **android_sdk.env,
+            **{"XDG_CONFIG_HOME": None},
+        },
     )
 
     # Emulator configuration file has been appended.
@@ -220,7 +226,10 @@ def test_create_failure(mock_tools, android_sdk):
             "--device",
             "pixel",
         ],
-        env=android_sdk.env,
+        env={
+            **android_sdk.env,
+            **{"XDG_CONFIG_HOME": None},
+        },
     )
 
 
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_verify.py b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
index 4e393a05..8a189816 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_verify.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
@@ -115,7 +115,7 @@ def test_supported_os_arch(mock_tools, host_os, host_arch, tmp_path):
 
     # Create `sdkmanager` and the license file.
     android_sdk_root_path = tmp_path / "tools/android_sdk"
-    tools_bin = android_sdk_root_path / "cmdline-tools/9.0/bin"
+    tools_bin = android_sdk_root_path / f"cmdline-tools/{SDK_MGR_VER}/bin"
     tools_bin.mkdir(parents=True, mode=0o755)
     if host_os == "Windows":
         sdk_manager = tools_bin / "sdkmanager.bat"
diff --git a/tests/integrations/android_sdk/conftest.py b/tests/integrations/android_sdk/conftest.py
index d63ea0e0..9dbb847d 100644
--- a/tests/integrations/android_sdk/conftest.py
+++ b/tests/integrations/android_sdk/conftest.py
@@ -10,8 +10,8 @@ from briefcase.integrations.java import JDK
 from briefcase.integrations.subprocess import Subprocess
 
 # current versions of Android SDK Manager
-SDK_MGR_VER = "9.0"
-SDK_MGR_DL_VER = "9477386"
+SDK_MGR_VER = "12.0"
+SDK_MGR_DL_VER = "11076708"
 
 
 @pytest.fixture
diff --git a/tests/integrations/subprocess/test_Subprocess__final_kwargs.py b/tests/integrations/subprocess/test_Subprocess__final_kwargs.py
index 6ac5bf8b..aec5cee5 100644
--- a/tests/integrations/subprocess/test_Subprocess__final_kwargs.py
+++ b/tests/integrations/subprocess/test_Subprocess__final_kwargs.py
@@ -49,6 +49,20 @@ def test_env_overrides(mock_sub):
     }
 
 
+def test_env_overrides_none(mock_sub):
+    """Any overrides set to None are absent in the final kwargs."""
+    assert mock_sub.final_kwargs(env={"NEWVAR": "val", "VAR1": None, "VAR2": None}) == {
+        "env": {
+            "PS1": "\nLine 2\n\nLine 4",
+            "PWD": "/home/user/",
+            "NEWVAR": "val",
+        },
+        "text": True,
+        "encoding": CONSOLE_ENCODING,
+        "errors": "backslashreplace",
+    }
+
+
 def test_cwd_provided(mock_sub):
     """If a cwd is provided, it is reflected in the environment."""
     cwd_override = "/my/current/path"