From 03d6e4bc46adfed68aab9969e6e61257d3177d2f Mon Sep 17 00:00:00 2001 From: Morgan Jones Date: Mon, 22 Dec 2025 23:45:00 -0800 Subject: [PATCH] androidenv: support nonstandard SDK versions Google have been releasing 'nonstandard' SDK versions for a while. Named things like CANARY, UpsideDownCake, 36x, and so on, androidenv has been generally unable to use them, instead preferring Google's officially supported Android SDKs corresponding to the [API levels](https://apilevels.com/) (e.g. "35" or "36"). Updates to those SDK versions have generally come in the form of updates to Google's repositories that repo.json picks up. These are mostly noneventful for end users, unless a user experienced a bug in an API definition, then it tended to be picked up with an androidenv repo update. API 36.1 changes this. It's not just a number (so it doesn't correspond to a unique API level). The previous attempt in #470569 simply preferred the newer version to attempt to emulate the old behavior. Unfortunately, this now requires a Gradle change (see #472561). So this means we should just support all the strange SDK versions now and have a test for it. Note that only depending on "latest" (the default) will properly pick these up as the latest, but using a min and max SDK version or numLatestPlatformVersions > 1 will create a range of SDK versions as before, which means you may miss (e.g.) 36.1 but will still have 36. Considering that this requires `compileSdk "android-36.1"` instead of a simple `compileSdk 36` in Gradle, it's recommended to explicitly specify platformVersions if you depend on this change, or use the defaults, where it will only install the latest. --- .../androidenv/compose-android-packages.nix | 69 ++++++++----------- .../examples/shell-with-emulator.nix | 15 ++-- .../examples/shell-without-emulator.nix | 17 +++-- .../mobile/androidenv/examples/shell.nix | 18 ++--- 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/pkgs/development/mobile/androidenv/compose-android-packages.nix b/pkgs/development/mobile/androidenv/compose-android-packages.nix index cce2f31a2797..4c5149032c02 100644 --- a/pkgs/development/mobile/androidenv/compose-android-packages.nix +++ b/pkgs/development/mobile/androidenv/compose-android-packages.nix @@ -24,7 +24,9 @@ let # Parses a list of versions, substituting "latest" with the latest version. parseVersions = repo: key: versions: - lib.unique (map (parseVersion repo key) versions); + lib.sort (a: b: lib.strings.compareVersions (toString a) (toString b) > 0) ( + lib.unique (map (parseVersion repo key) versions) + ); in { repoJson ? ./repo.json, @@ -98,14 +100,27 @@ in numLatestPlatformVersions ? 1, platformVersions ? if minPlatformVersion != null && maxPlatformVersion != null then + # Range between min and max, inclusive. let - minPlatformVersionInt = coerceIntVersion (parseVersion repo "platforms" minPlatformVersion); - maxPlatformVersionInt = coerceIntVersion (parseVersion repo "platforms" maxPlatformVersion); + minPlatformVersion' = parseVersion repo "platforms" minPlatformVersion; + maxPlatformVersion' = parseVersion repo "platforms" maxPlatformVersion; + minPlatformVersionInt = coerceIntVersion minPlatformVersion'; + maxPlatformVersionInt = coerceIntVersion maxPlatformVersion'; + range = lib.range (lib.min minPlatformVersionInt maxPlatformVersionInt) ( + lib.max minPlatformVersionInt maxPlatformVersionInt + ); in - lib.range (lib.min minPlatformVersionInt maxPlatformVersionInt) ( - lib.max minPlatformVersionInt maxPlatformVersionInt - ) + # Don't use the actual latest version in lieu of the rounded version here, + # since when Google upgrades it would have the nasty side effect of being + # unstable and picking 35 -> 36 -> 37 instead of 35 -> 36.1 -> 37. + # Best to stay consistent. + # + # However, if only one platform is requested and it's the latest (which is the default), + # we should use it. + if lib.length range == 1 then lib.singleton maxPlatformVersion' else range else + # Use numLatestPlatformVersions with a lower cutoff of minPlatformVersion (defaulting to 1) + # to determine how many of the latest *major* versions we should pick. let minPlatformVersionInt = if minPlatformVersion == null then @@ -116,8 +131,10 @@ in firstPlatformVersionInt = lib.max minPlatformVersionInt ( latestPlatformVersionInt - (lib.max 1 numLatestPlatformVersions) + 1 ); + range = lib.range firstPlatformVersionInt latestPlatformVersionInt; in - lib.range firstPlatformVersionInt latestPlatformVersionInt, + # Ditto, see above. + if lib.length range == 1 then lib.singleton repo.latest.platforms else range, includeSources ? false, includeSystemImages ? false, systemImageTypes ? [ @@ -144,7 +161,7 @@ in let # Resolve all the platform versions. - platformVersions' = map coerceIntVersion (parseVersions repo "platforms" platformVersions); + platformVersions' = parseVersions repo "platforms" platformVersions; # Determine the Android os identifier from Nix's system identifier os = @@ -220,28 +237,6 @@ let extras = fetchArchives repo.extras; }; - # Latest packages that are typically keyed by the API level. - archivesByApiLevel = - let - # Transforms the given attrset mapping API levels (with possible suffixes and minor versions) - # into the latest API level for each major version. - mkLatestByApiLevel = - packages: - lib.filterAttrs (_: value: value != null) ( - lib.mapAttrs ( - _: value: - (lib.findFirst (x: x.name == lib.versions.majorMinor x.name) { value = null; } ( - lib.lists.sort (x: y: lib.strings.versionOlder y.name x.name) value - )).value - ) (lib.groupBy (x: lib.versions.major x.name) (lib.attrsToList packages)) - ); - in - { - platforms = mkLatestByApiLevel allArchives.packages.platforms; - sources = mkLatestByApiLevel allArchives.packages.sources; - system-images = mkLatestByApiLevel allArchives.system-images; - }; - # Lift the archives to the package level for easy search, # and add recurseIntoAttrs to all of them. allPackages = @@ -547,17 +542,14 @@ lib.recurseIntoAttrs rec { platforms = map ( version: deployAndroidPackage { - package = checkVersion [ archivesByApiLevel allArchives.packages ] "platforms" version; + package = checkVersion allArchives.packages "platforms" version; } ) platformVersions'; - # This exposes the version strings (e.g. "36.1" for API 36). - platformVersionStrings = map (platform: platform.version) platforms; - sources = map ( version: deployAndroidPackage { - package = checkVersion [ archivesByApiLevel allArchives.packages ] "sources" version; + package = checkVersion allArchives.packages "sources" version; } ) platformVersions'; @@ -576,12 +568,7 @@ lib.recurseIntoAttrs rec { # ``` let availablePackages = - map - ( - abiVersion: - archivesByApiLevel.system-images.${toString apiVersion}.${type}.${abiVersion} - or allArchives.system-images.${toString apiVersion}.${type}.${abiVersion} - ) + map (abiVersion: allArchives.system-images.${toString apiVersion}.${type}.${abiVersion}) ( builtins.filter ( abiVersion: lib.hasAttrByPath [ (toString apiVersion) type abiVersion ] allArchives.system-images diff --git a/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix b/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix index 9c6d9ec60902..042ff4ef43d1 100644 --- a/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix +++ b/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix @@ -39,6 +39,8 @@ let }; */ + inherit (pkgs) lib; + # Otherwise, just use the in-tree androidenv: androidEnv = pkgs.callPackage ./.. { inherit pkgs licenseAccepted; @@ -77,10 +79,9 @@ let }; androidSdk = androidComposition.androidsdk; platformTools = androidComposition.platform-tools; - latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions; - latestSdkVersion = pkgs.lib.foldl' ( - s: x: if pkgs.lib.strings.compareVersions x s > 0 then x else s - ) "0" androidComposition.platformVersionStrings; + latestSdkVersion = lib.foldl' ( + s: x: if lib.strings.compareVersions (toString x) s > 0 then toString x else s + ) "0" androidComposition.platformVersions; jdk = pkgs.jdk; in pkgs.mkShell rec { @@ -129,7 +130,7 @@ pkgs.mkShell rec { "platform-tools" "platforms;android-${toString latestSdkVersion}" \ "system-images;android-${toString latestSdkVersion};google_apis;x86_64" ) - ${pkgs.lib.optionalString emulatorSupported ''packages+=("emulator")''} + ${lib.optionalString emulatorSupported ''packages+=("emulator")''} for package in "''${packages[@]}"; do if [[ ! $installed_packages_section =~ "$package" ]]; then @@ -154,7 +155,7 @@ pkgs.mkShell rec { installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}') excluded_packages=(ndk) - for x in $(seq 1 ${toString latestSdk}); do + for x in $(seq 1 ${lib.versions.major (toString latestSdkVersion)}); do excluded_packages+=( "platforms;android-$x" "sources;android-$x" @@ -182,7 +183,7 @@ pkgs.mkShell rec { ]; } ( - pkgs.lib.optionalString emulatorSupported '' + lib.optionalString emulatorSupported '' export ANDROID_USER_HOME=$PWD/.android mkdir -p $ANDROID_USER_HOME diff --git a/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix b/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix index 58e7879dc3ee..353379aca50f 100644 --- a/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix +++ b/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix @@ -38,6 +38,8 @@ let }; */ + inherit (pkgs) lib; + # Otherwise, just use the in-tree androidenv: androidEnv = pkgs.callPackage ./.. { inherit pkgs licenseAccepted; @@ -48,7 +50,13 @@ let includeSystemImages = false; includeEmulator = false; - platformVersions = [ "latest" ]; + platformVersions = [ + "UpsideDownCake" + "36" + "36x" + "latest" + "CANARY" + ]; # Accepting more licenses declaratively: extraLicenses = [ @@ -70,7 +78,9 @@ let androidComposition = androidEnv.composeAndroidPackages sdkArgs; androidSdk = androidComposition.androidsdk; platformTools = androidComposition.platform-tools; - latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions; + latestSdkVersion = lib.foldl' ( + s: x: if lib.strings.compareVersions (toString x) s > 0 then toString x else s + ) "0" androidComposition.platformVersions; jdk = pkgs.jdk; in pkgs.mkShell { @@ -97,7 +107,6 @@ pkgs.mkShell { ''; passthru.tests = { - shell-without-emulator-sdkmanager-packages-test = pkgs.runCommand "shell-without-emulator-sdkmanager-packages-test" { @@ -113,7 +122,7 @@ pkgs.mkShell { packages=( "build-tools" "cmdline-tools" \ - "platform-tools" "platforms;android-${toString latestSdk}" + "platform-tools" "platforms;android-${toString latestSdkVersion}" ) for package in "''${packages[@]}"; do diff --git a/pkgs/development/mobile/androidenv/examples/shell.nix b/pkgs/development/mobile/androidenv/examples/shell.nix index 7fc69c3c1ca8..5cf8383388f5 100644 --- a/pkgs/development/mobile/androidenv/examples/shell.nix +++ b/pkgs/development/mobile/androidenv/examples/shell.nix @@ -37,6 +37,8 @@ let }; */ + inherit (pkgs) lib; + # Otherwise, just use the in-tree androidenv: androidEnv = pkgs.callPackage ./.. { inherit pkgs licenseAccepted; @@ -109,8 +111,12 @@ let androidSdk = androidComposition.androidsdk; platformTools = androidComposition.platform-tools; - firstSdk = pkgs.lib.foldl' pkgs.lib.min 100 androidComposition.platformVersions; - latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions; + firstSdkVersion = lib.foldl' ( + s: x: if lib.strings.compareVersions (toString x) s < 0 then toString x else s + ) "100" androidComposition.platformVersions; + latestSdkVersion = lib.foldl' ( + s: x: if lib.strings.compareVersions (toString x) s > 0 then toString x else s + ) "0" androidComposition.platformVersions; jdk = pkgs.jdk; in pkgs.mkShell rec { @@ -181,15 +187,11 @@ pkgs.mkShell rec { "extras;google;gcm" ) - for x in $(seq ${toString firstSdk} ${toString latestSdk}); do - if [ $x -ne 34 ]; then - # FIXME couldn't find platforms;android-34, even though it's in the correct directory!! sdkmanager's bug?! - packages+=("platforms;android-$x") - fi + for x in $(seq ${toString firstSdkVersion} ${toString latestSdkVersion}); do packages+=("sources;android-$x") done - ${pkgs.lib.optionalString includeAuto ''packages+=("extras;google;auto")''} + ${lib.optionalString includeAuto ''packages+=("extras;google;auto")''} for package in "''${packages[@]}"; do if [[ ! $installed_packages_section =~ "$package" ]]; then