Repository: beeware/briefcase
Pull request #1382 (2023-07-24 17:43:39)

Improve Tool verification logging and feedback
## Changes
- Put breadcrumbs in debug log during tool verification to better understand what happened
- Inform users they can set `JAVA_HOME` and `ANDROID_HOME` when Briefcase installs them
- Provide feedback in errors on how `JAVA_HOME` and `ANDROID_HOME` should be specified
- This also better aligns the tools with how I implemented logging Windows SDK verification

Install messages:
```console
❯ briefcase build android

[java] A Java 17 JDK was not found; downloading and installing...
To use an existing JDK 17 instance, specify its root directory path in the JAVA_HOME environment variable.

Downloading OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz...
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 00:03
Installing OpenJDK... done

[android_sdk] The Android SDK was not found; downloading and installing...
To use an existing Android SDK instance, specify its root directory path in the ANDROID_HOME environment variable.

Downloading commandlinetools-linux-8092744_latest.zip...
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 00:03
Installing Android SDK Command-Line Tools... done
```

Valid environment variables:
```console
❯ briefcase build android -v

>>> [Java JDK] Evaluating JAVA_HOME...
>>> JAVA_HOME=/home/russell/tmp/jdk-17.0.8+7/

>>> Running Command:
>>>     /home/russell/tmp/jdk-17.0.8+7/bin/javac -version
>>> Working Directory:
>>>     /home/russell/tmp/beeware/helloworld
>>> Command Output:
>>>     javac 17.0.8
>>> Return code: 0
>>> Using JDK at /home/russell/tmp/jdk-17.0.8+7

>>> [Android SDK] Evaluating ANDROID_HOME...
>>> ANDROID_HOME=/home/russell/.cache/briefcase/tools/android_sdk
>>> Using Android SDK at /home/russell/.cache/briefcase/tools/android_sdk
```
Bad `JAVA_HOME` value:
```console
❯ briefcase build android -v

>>> [Java JDK] Evaluating JAVA_HOME...
>>> JAVA_HOME=/home/russell/tmp/jdk-17.0.8+7/bin

>>> Running Command:
>>>     /home/russell/tmp/jdk-17.0.8+7/bin/bin/javac -version
>>> Working Directory:
>>>     /home/russell/tmp/beeware/helloworld

*************************************************************************
** WARNING: JAVA_HOME does not point to a JDK                          **
*************************************************************************

    The location pointed to by the JAVA_HOME environment variable:

    /home/russell/tmp/jdk-17.0.8+7/bin

    does not appear to be a JDK. It may be a Java Runtime Environment.

    If JAVA_HOME is a JDK, ensure it is the root directory of the JDK
    instance such that $JAVA_HOME/bin/javac is a valid filepath.

    Briefcase will proceed using its own JDK instance.

*************************************************************************
>>> Using JDK at /home/russell/.cache/briefcase/tools/java17

>>> [Android SDK] Evaluating ANDROID_HOME...
>>> ANDROID_HOME=/home/russell/.cache/briefcase/tools/android_sdk
>>> Using Android SDK at /home/russell/.cache/briefcase/tools/android_sdk
```

Bad `ANDROID_HOME` value:
```console
❯ briefcase build android -v

>>> [Java JDK] Evaluating JAVA_HOME...
>>> JAVA_HOME=/home/russell/tmp/jdk-17.0.8+7/

>>> Running Command:
>>>     /home/russell/tmp/jdk-17.0.8+7/bin/javac -version
>>> Working Directory:
>>>     /home/russell/tmp/beeware/helloworld
>>> Command Output:
>>>     javac 17.0.8
>>> Return code: 0
>>> Using JDK at /home/russell/tmp/jdk-17.0.8+7

>>> [Android SDK] Evaluating ANDROID_HOME...
>>> ANDROID_HOME=/home/russell/.cache/briefcase/tools/android_sdk/bin

*************************************************************************
** WARNING: ANDROID_HOME does not point to an Android SDK              **
*************************************************************************

    The location pointed to by the ANDROID_HOME environment
    variable:

    /home/russell/.cache/briefcase/tools/android_sdk/bin

    doesn't appear to contain an Android SDK.

    If ANDROID_HOME is an Android SDK, ensure it is the root directory
    of the Android SDK instance such that

    $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager

    is a valid filepath.

    Briefcase will proceed using its own SDK instance.

*************************************************************************
>>> Using Android SDK at /home/russell/.cache/briefcase/tools/android_sdk
```
## 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



Patch:
diff --git a/changes/1382.feature.rst b/changes/1382.feature.rst
new file mode 100644
index 00000000..2e45dae0
--- /dev/null
+++ b/changes/1382.feature.rst
@@ -0,0 +1,1 @@
+Tool verification for Java, Android SDK, and WiX were improved to provide more informative errors and debug logging.
diff --git a/src/briefcase/integrations/android_sdk.py b/src/briefcase/integrations/android_sdk.py
index 2147ab07..14742c16 100644
--- a/src/briefcase/integrations/android_sdk.py
+++ b/src/briefcase/integrations/android_sdk.py
@@ -229,6 +229,8 @@ class AndroidSDK(ManagedTool):
         sdk_root, sdk_env_source = cls.sdk_path_from_env(tools=tools)
 
         if sdk_root:
+            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))
 
             if sdk.exists():
@@ -267,7 +269,14 @@ class AndroidSDK(ManagedTool):
 
     doesn't appear to contain an Android SDK.
 
-    Briefcase will use its own SDK instance.
+    If {sdk_env_source} 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
+
+    is a valid filepath.
+
+    Briefcase will proceed using its own SDK instance.
 
 *************************************************************************
 """
@@ -311,10 +320,16 @@ class AndroidSDK(ManagedTool):
                         "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")
 
+        tools.logger.debug(f"Using Android SDK at {sdk.root_path}")
         tools.android_sdk = sdk
         return sdk
 
diff --git a/src/briefcase/integrations/java.py b/src/briefcase/integrations/java.py
index 69f5bf8b..307848af 100644
--- a/src/briefcase/integrations/java.py
+++ b/src/briefcase/integrations/java.py
@@ -101,6 +101,8 @@ class JDK(ManagedTool):
         install_message = None
 
         if java_home := tools.os.environ.get("JAVA_HOME", ""):
+            tools.logger.debug("Evaluating JAVA_HOME...", prefix=cls.full_name)
+            tools.logger.debug(f"JAVA_HOME={java_home}")
             try:
                 version_str = cls.version_from_path(tools, java_home)
                 if version_str.split(".")[0] == cls.JDK_MAJOR_VER:
@@ -118,7 +120,7 @@ class JDK(ManagedTool):
 
     isn't a Java {cls.JDK_MAJOR_VER} JDK (it appears to be Java {version_str}).
 
-    Briefcase will use its own JDK instance.
+    Briefcase will proceed using its own JDK instance.
 
 *************************************************************************
 """
@@ -135,7 +137,10 @@ class JDK(ManagedTool):
 
     does not appear to be a JDK. It may be a Java Runtime Environment.
 
-    Briefcase will use its own JDK instance.
+    If JAVA_HOME is a JDK, ensure it is the root directory of the JDK
+    instance such that $JAVA_HOME/bin/javac is a valid filepath.
+
+    Briefcase will proceed using its own JDK instance.
 
 *************************************************************************
 """
@@ -194,6 +199,9 @@ class JDK(ManagedTool):
 
         # macOS has a helpful system utility to determine JAVA_HOME. Try it.
         elif tools.host_os == "Darwin":
+            tools.logger.debug(
+                "Evaluating /usr/libexec/java_home...", prefix=cls.full_name
+            )
             try:
                 # If /usr/libexec/java_home doesn't exist, OSError will be raised
                 # If no JRE/JDK is installed, /usr/libexec/java_home raises an error
@@ -201,7 +209,7 @@ class JDK(ManagedTool):
                     ["/usr/libexec/java_home"],
                 ).strip("\n")
             except (OSError, subprocess.CalledProcessError):
-                pass  # No java on this machine
+                tools.logger.debug("An existing JDK/JRE was not returned")
             else:
                 try:
                     version_str = cls.version_from_path(tools, java_home)
@@ -231,10 +239,16 @@ class JDK(ManagedTool):
                         f"A Java {cls.JDK_MAJOR_VER} JDK was not found; downloading and installing...",
                         prefix=cls.name,
                     )
+                    tools.logger.info(
+                        f"To use an existing JDK {cls.JDK_MAJOR_VER} instance, "
+                        f"specify its root directory path in the JAVA_HOME environment variable."
+                    )
+                    tools.logger.info()
                     java.install()
                 else:
                     raise MissingToolError("Java")
 
+        tools.logger.debug(f"Using JDK at {java.java_home}")
         tools.java = java
         return java
 
diff --git a/src/briefcase/integrations/wix.py b/src/briefcase/integrations/wix.py
index 3cb97225..3424bcf1 100644
--- a/src/briefcase/integrations/wix.py
+++ b/src/briefcase/integrations/wix.py
@@ -76,8 +76,9 @@ class WiX(ManagedTool):
             return tools.wix
 
         # Look for the WIX environment variable
-        wix_env = tools.os.environ.get("WIX")
-        if wix_env:
+        if wix_env := tools.os.environ.get("WIX"):
+            tools.logger.debug("Evaluating WIX...", prefix=cls.full_name)
+            tools.logger.debug(f"WIX={wix_env}")
             wix_home = Path(wix_env)
 
             # Set up the paths for the WiX executables we will use.
@@ -107,6 +108,7 @@ does not point to an install of the WiX Toolset.
                 else:
                     raise MissingToolError("WiX")
 
+        tools.logger.debug(f"Using WiX at {wix.wix_home}")
         tools.wix = wix
         return wix
 


Test patch:
diff --git a/tests/integrations/android_sdk/AndroidSDK/test_verify.py b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
index c3871898..479a2f8b 100644
--- a/tests/integrations/android_sdk/AndroidSDK/test_verify.py
+++ b/tests/integrations/android_sdk/AndroidSDK/test_verify.py
@@ -150,7 +150,11 @@ def test_user_provided_sdk(mock_tools, env_var, tmp_path, capsys):
     if env_var == "ANDROID_SDK_ROOT":
         assert "Using Android SDK from ANDROID_SDK_ROOT" in capsys.readouterr().out
     else:
-        assert capsys.readouterr().out == ""
+        assert capsys.readouterr().out == (
+            "\n>>> [Android SDK] Evaluating ANDROID_HOME...\n"
+            f">>> ANDROID_HOME={tmp_path / 'other_sdk'}\n"
+            f">>> Using Android SDK at {tmp_path / 'other_sdk'}\n"
+        )
 
 
 def test_consistent_user_provided_sdk(mock_tools, tmp_path, capsys):
@@ -190,7 +194,11 @@ def test_consistent_user_provided_sdk(mock_tools, tmp_path, capsys):
     assert sdk.root_path == existing_android_sdk_root_path
 
     # User is not warned about configuration
-    assert capsys.readouterr().out == ""
+    assert capsys.readouterr().out == (
+        "\n>>> [Android SDK] Evaluating ANDROID_HOME...\n"
+        f">>> ANDROID_HOME={tmp_path / 'other_sdk'}\n"
+        f">>> Using Android SDK at {tmp_path / 'other_sdk'}\n"
+    )
 
 
 def test_inconsistent_user_provided_sdk(mock_tools, tmp_path, capsys):