Skip to content
Open
Changes from all 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
77 changes: 76 additions & 1 deletion uraniborg/scripts/python/automate_observation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/python3
# Copyright 2020 Uraniborg authors.
# Copyright 2026 Uraniborg authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -736,6 +736,77 @@ def extract_selinux_policies(adb_wrapper: syscall_wrapper.AdbWrapper,
logger.warning("Failed to pull %s. Continuing...", source_location)


def ensure_android_sdk(hubble_project_dir: str, logger: logging.Logger) -> bool:
"""Ensures that Android SDK location is set for Gradle.

Args:
hubble_project_dir: Path to the Hubble Android project directory.
logger: A logger object to log debug or error messages.

Returns:
True if SDK location is set and valid, False otherwise.
"""
if os.environ.get("ANDROID_HOME"):
logger.debug("ANDROID_HOME is set to %s", os.environ.get("ANDROID_HOME"))
return True
if os.environ.get("ANDROID_SDK_ROOT"):
logger.debug("ANDROID_SDK_ROOT is set to %s", os.environ.get("ANDROID_SDK_ROOT"))
return True
Comment on lines +749 to +754
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The environment variable check should verify that the directory actually exists, consistent with how local.properties and default locations are handled. This ensures the build doesn't fail later due to an invalid path in the environment.

Suggested change
if os.environ.get("ANDROID_HOME"):
logger.debug("ANDROID_HOME is set to %s", os.environ.get("ANDROID_HOME"))
return True
if os.environ.get("ANDROID_SDK_ROOT"):
logger.debug("ANDROID_SDK_ROOT is set to %s", os.environ.get("ANDROID_SDK_ROOT"))
return True
for env_var in ["ANDROID_HOME", "ANDROID_SDK_ROOT"]:
val = os.environ.get(env_var)
if val:
logger.debug("%s is set to %s", env_var, val)
if os.path.exists(val):
return True
logger.warning("%s points to non-existent directory: %s", env_var, val)


local_props_path = os.path.join(hubble_project_dir, "local.properties")
if os.path.exists(local_props_path):
with open(local_props_path, "r") as f:
for line in f:
if line.startswith("sdk.dir="):
sdk_dir = line.split("=")[1].strip()
logger.debug("Found sdk.dir in local.properties: %s", sdk_dir)
if os.path.exists(sdk_dir):
return True
else:
logger.warning("sdk.dir in local.properties points to non-existent directory: %s", sdk_dir)
Comment on lines +760 to +766
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The parsing of local.properties is brittle. It doesn't handle potential whitespace around the = sign and might fail if the value itself contains an = character.

Suggested change
if line.startswith("sdk.dir="):
sdk_dir = line.split("=")[1].strip()
logger.debug("Found sdk.dir in local.properties: %s", sdk_dir)
if os.path.exists(sdk_dir):
return True
else:
logger.warning("sdk.dir in local.properties points to non-existent directory: %s", sdk_dir)
parts = line.split("=", 1)
if len(parts) == 2 and parts[0].strip() == "sdk.dir":
sdk_dir = parts[1].strip()
logger.debug("Found sdk.dir in local.properties: %s", sdk_dir)
if os.path.exists(sdk_dir):
return True
else:
logger.warning("sdk.dir in local.properties points to non-existent directory: %s", sdk_dir)


default_locations = []
if sys.platform == "darwin":
default_locations.append(os.path.expanduser("~/Library/Android/sdk"))
elif sys.platform == "linux":
default_locations.append(os.path.expanduser("~/Android/Sdk"))

sdk_path = None
for loc in default_locations:
if os.path.exists(loc):
logger.info("Auto-detected Android SDK at %s", loc)
sdk_path = loc
break

if not sdk_path:
logger.warning("Android SDK location not found in environment or default locations.")
try:
user_input = input("Please enter the path to your Android SDK: ").strip()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using input() in a script intended for automation can cause it to hang or fail in non-interactive environments (like CI/CD). Consider allowing the SDK path to be passed as a command-line argument or strictly relying on environment variables for such cases.

if user_input:
sdk_path = os.path.abspath(os.path.expanduser(user_input))
except (KeyboardInterrupt, EOFError):
logger.error("Input cancelled.")
return False

if not sdk_path or not os.path.exists(sdk_path):
logger.error("Invalid or non-existent Android SDK path.")
return False

logger.info("Writing sdk.dir to %s", local_props_path)
lines = []
if os.path.exists(local_props_path):
with open(local_props_path, "r") as f:
lines = f.readlines()

lines = [l for l in lines if not l.startswith("sdk.dir=")]
lines.append(f"sdk.dir={sdk_path}\n")
Comment on lines +801 to +802
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When appending to local.properties, if the existing file does not end with a newline character, the new entry will be appended to the last line, making it invalid. Additionally, the filtering logic should be more robust against leading whitespace.

  lines = [l for l in lines if not l.strip().startswith("sdk.dir=")]
  if lines and not lines[-1].endswith("\n"):
    lines[-1] += "\n"
  lines.append(f"sdk.dir={sdk_path}\n")


with open(local_props_path, "w") as f:
f.writelines(lines)

return True


def main():
args = parse_arguments()
logger = set_up_logging(args)
Expand All @@ -748,6 +819,9 @@ def main():
logger.info("-H flag not used. Rebuilding Hubble...")
script_dir = os.path.dirname(os.path.abspath(__file__))
hubble_project_dir = os.path.abspath(os.path.join(script_dir, "../../AndroidStudioProject/Hubble"))
if not ensure_android_sdk(hubble_project_dir, logger):
logger.error("Android SDK not found or configured. Cannot build Hubble.")
return
gradlew_path = os.path.join(hubble_project_dir, "gradlew")

logger.info("Running 'gradlew assemble' in %s", hubble_project_dir)
Expand All @@ -764,6 +838,7 @@ def main():
logger.debug("Gradle output: %s", line)

latest_symlink_path = os.path.abspath(os.path.join(script_dir, "../../prebuilts/APK/latest"))
os.makedirs(os.path.dirname(latest_symlink_path), exist_ok=True)
if os.path.exists(latest_symlink_path) or os.path.islink(latest_symlink_path):
logger.debug("Removing old symlink: %s", latest_symlink_path)
try:
Expand Down