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

Avoid panicking when Android SDK platform version or build tools version is unspecified #3

Open
wuwbobo2021 opened this issue Jan 11, 2025 · 0 comments

Comments

@wuwbobo2021
Copy link

This behavior had caused the first problem described in wuwbobo2021/android-usbser-rs#3, that I decided to remove android-build build dependency, easing compilation of downstream crates: environment variables required by this crate aren't listed in https://developer.android.com/tools/variables, and this crate is currently not popular. I suggest that android_jar and android_d8_jar can return None or some Error.

How about making this modification:

/// Returns the path to the `android.jar` file for the given API level.
///
/// The path is determined by an ordered set of attempts:
/// * The `ANDROID_JAR` environment variable, if it is set and points to a file that exists.
/// * The argument `platform_string` is used if it is `Some`, to find the subdirectory for
///   the specific platform version under the Android SDK `platforms` directory, in which
///   the `android.jar` file should exist.
/// * The value of the following environment variables are used to calculate the platform string:
///   * `ANDROID_PLATFORM`, `ANDROID_API_LEVEL` or `ANDROID_SDK_VERSION`
///   * `ANDROID_SDK_EXTENSION` (optional)
/// * The highest Android platform version found in the SDK `platforms` directory is used if
///   the platform version is not set by environment variables.
pub fn android_jar(platform_string: Option<&str>) -> Option<PathBuf> {
    env::var(ANDROID_JAR).ok()
        .and_then(PathExt::path_if_exists)
        .map(PathBuf::from)
        .or_else(|| android_sdk()
            .map(|sdk| sdk.join("platforms"))
            .and_then(|platforms| {
                platform_string.map(ToString::to_string)
                    .or_else(|| env_android_platform_api_level())
                    .or_else(|| find_latest_version(&platforms, "android.jar"))
                    .map(|version| platforms.join(version))
            })
        )
        .and_then(|path| path.join("android.jar").path_if_exists())
}

/// Returns the path to the `d8.jar` file for the given build tools version.
///
/// The path is determined by an ordered set of attempts:
/// * The `ANDROID_D8_JAR` environment variable, if it is set and points to a file that exists.
/// * The `ANDROID_BUILD_TOOLS_VERSION` environment variable is used to find the `d8.jar` file
///   from the Android SDK root directory.
/// * The highest Android build tools version found in the SDK `build-tools` directory is used if
///   the build tools version is not set by the environment variable.
pub fn android_d8_jar(build_tools_version: Option<&str>) -> Option<PathBuf> {
    env::var(ANDROID_D8_JAR).ok()
        .and_then(PathExt::path_if_exists)
        .map(PathBuf::from)
        .or_else(|| android_sdk()
            .map(|sdk| sdk.join("build-tools"))
            .and_then(|build_tools| {
                build_tools_version.map(ToString::to_string)
                    .or_else(|| env::var(ANDROID_BUILD_TOOLS_VERSION).ok())
                    .or_else(|| find_latest_version(&build_tools, Path::new("lib").join("d8.jar")))
                    .map(|version| build_tools.join(version))
            })
            .and_then(|path| path.join("lib").join("d8.jar").path_if_exists())
        )
}

/// Finds subdirectories in which the subpath `arg` exists, and returns the maximum
/// item name in lexicographical order based on `Ord` impl of `std::path::Path`.
/// NOTE: the behavior can be changed in the future.
/// 
/// Code inspired by <https://docs.rs/crate/i-slint-backend-android-activity/1.9.1/source/build.rs>.
fn find_latest_version(base: impl AsRef<Path>, arg: impl AsRef<Path>) -> Option<String> {
    std::fs::read_dir(base)
        .ok()?
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.path().join(arg.as_ref()).exists())
        .map(|entry| entry.file_name())
        .max()
        .and_then(|name| name.to_os_string().into_string().ok())
}

Theses are functions modified by myself, but some code printing warning messages can be added to indicate that these versions are unspecified. That is, to have println!("cargo::warning={}", msg); if it's being called in a build script (CARGO_MANIFEST_DIR is an existing environment variable), otherwise just do eprintln("{}", msg).

Notes:

A few related issues: alexmoon/bluest#9, slint-ui/slint#4973

In https://developer.android.com/tools/d8/, there's an example usage of d8 with argument --lib android_sdk/platforms/api-level/android.jar, not the --classpath android_sdk/platforms/api-level/android.jar (found in https://docs.rs/crate/robius-authentication/latest/source/build.rs). In the build script of i-slint-backend-android-activity, a classpath of javac output path is specified for d8 (but no android.jar), probably because of multiple class definitions inside SlintAndroidJavaHelper.java.

Would you like to have a PR for Dexer implementation? What is the functionality that Dexer should expose? Should it be a simple d8 command builder having Java class/jar/dex file as its input, or a easy-to-use module for generating the dex file from Java code in given source path (still being able to add .jar dependencies to be used as a class path for javac and an input for d8)? I guess a simple Dexer implementation can be added easily, so that more parameters can be added in the future without breaking SemVer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant