From dcac273d94f52dae213b991ebf2e93f4dad60ad7 Mon Sep 17 00:00:00 2001 From: Morgan Jones Date: Sat, 13 Dec 2025 16:20:17 -0800 Subject: [PATCH] androidenv: fix 'latest' where SDK level is not an int Map 'latest' to the latest minor version of each SDK level, since Google now has minor versions of SDK levels. The SDK levels are still integers, so also map "36" to (e.g.) "36.1" by default, since the user likely wants the latest revision of "36" we have. Fix: #468525 --- .../androidenv/compose-android-packages.nix | 66 +++++++++++++++---- .../examples/shell-with-emulator.nix | 9 ++- .../examples/shell-without-emulator.nix | 2 + 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/pkgs/development/mobile/androidenv/compose-android-packages.nix b/pkgs/development/mobile/androidenv/compose-android-packages.nix index 33332ea8386d..cce2f31a2797 100644 --- a/pkgs/development/mobile/androidenv/compose-android-packages.nix +++ b/pkgs/development/mobile/androidenv/compose-android-packages.nix @@ -14,7 +14,7 @@ let # Coerces a string to an int. coerceInt = val: if lib.isInt val then val else lib.toIntBase10 val; - coerceIntVersion = v: coerceInt (lib.versions.major v); + coerceIntVersion = v: coerceInt (lib.versions.major (toString v)); # Parses a single version, substituting "latest" with the latest version. parseVersion = @@ -144,7 +144,7 @@ in let # Resolve all the platform versions. - platformVersions' = map coerceInt (parseVersions repo "platforms" platformVersions); + platformVersions' = map coerceIntVersion (parseVersions repo "platforms" platformVersions); # Determine the Android os identifier from Nix's system identifier os = @@ -220,6 +220,28 @@ 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 = @@ -284,17 +306,28 @@ let lib.hasAttrByPath [ package (toString version) ] packages; # Displays a nice error message that includes the available options if a version doesn't exist. + # Note that allPackages can be a list of package sets, or a single package set. Pass a list if + # you want to prioritize elements to the left (e.g. for passing a platform major version). checkVersion = - packages: package: version: - if hasVersion packages package version then - packages.${package}.${toString version} - else + allPackages: package: version: + let + # Convert the package sets to a list. + allPackages' = if lib.isList allPackages then allPackages else lib.singleton allPackages; + + # Pick the first package set where we have the version. + packageSet = lib.findFirst (packages: hasVersion packages package version) null allPackages'; + in + if packageSet == null then throw '' The version ${toString version} is missing in package ${package}. The only available versions are ${ - builtins.concatStringsSep ", " (builtins.attrNames packages.${package}) + lib.concatStringsSep ", " ( + lib.attrNames (lib.foldl (s: x: s // (x.${package} or { })) { } allPackages') + ) }. - ''; + '' + else + packageSet.${package}.${toString version}; # Returns true if we should link the specified plugins. shouldLink = @@ -508,19 +541,23 @@ lib.recurseIntoAttrs rec { ''; }; + # This is a list of the chosen API levels, as integers. platformVersions = platformVersions'; platforms = map ( version: deployAndroidPackage { - package = checkVersion allArchives.packages "platforms" version; + package = checkVersion [ archivesByApiLevel 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 allArchives.packages "sources" version; + package = checkVersion [ archivesByApiLevel allArchives.packages ] "sources" version; } ) platformVersions'; @@ -539,14 +576,19 @@ lib.recurseIntoAttrs rec { # ``` let availablePackages = - map (abiVersion: allArchives.system-images.${toString apiVersion}.${type}.${abiVersion}) + map + ( + abiVersion: + archivesByApiLevel.system-images.${toString apiVersion}.${type}.${abiVersion} + or allArchives.system-images.${toString apiVersion}.${type}.${abiVersion} + ) ( builtins.filter ( abiVersion: lib.hasAttrByPath [ (toString apiVersion) type abiVersion ] allArchives.system-images ) abiVersions ); - instructions = builtins.listToAttrs ( + instructions = lib.listToAttrs ( map (package: { name = package.name; value = lib.optionalString (lib.hasPrefix "google_apis" type) '' diff --git a/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix b/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix index fbb52d65a23b..9c6d9ec60902 100644 --- a/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix +++ b/pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix @@ -78,6 +78,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; jdk = pkgs.jdk; in pkgs.mkShell rec { @@ -123,8 +126,8 @@ pkgs.mkShell rec { packages=( "build-tools" "cmdline-tools" \ - "platform-tools" "platforms;android-${toString latestSdk}" \ - "system-images;android-${toString latestSdk};google_apis;x86_64" + "platform-tools" "platforms;android-${toString latestSdkVersion}" \ + "system-images;android-${toString latestSdkVersion};google_apis;x86_64" ) ${pkgs.lib.optionalString emulatorSupported ''packages+=("emulator")''} @@ -184,7 +187,7 @@ pkgs.mkShell rec { mkdir -p $ANDROID_USER_HOME avdmanager delete avd -n testAVD || true - echo "" | avdmanager create avd --force --name testAVD --package 'system-images;android-${toString latestSdk};google_apis;x86_64' + echo "" | avdmanager create avd --force --name testAVD --package 'system-images;android-${toString latestSdkVersion};google_apis;x86_64' result=$(avdmanager list avd) if [[ ! $result =~ "Name: testAVD" ]]; then diff --git a/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix b/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix index ead2c48aaf26..58e7879dc3ee 100644 --- a/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix +++ b/pkgs/development/mobile/androidenv/examples/shell-without-emulator.nix @@ -48,6 +48,8 @@ let includeSystemImages = false; includeEmulator = false; + platformVersions = [ "latest" ]; + # Accepting more licenses declaratively: extraLicenses = [ # Already accepted for you with the global accept_license = true or