1{ 2 composeAndroidPackages, 3 stdenv, 4 lib, 5 runtimeShell, 6 meta, 7}: 8{ 9 name, 10 app ? null, 11 platformVersion ? "35", 12 abiVersion ? "x86", 13 systemImageType ? "default", 14 enableGPU ? false, # Enable GPU acceleration. It's deprecated, instead use `configOptions` below. 15 configOptions ? ( 16 # List of options to add in config.ini 17 lib.optionalAttrs enableGPU ( 18 lib.warn "enableGPU argument is deprecated and will be removed; use configOptions instead" { 19 "hw.gpu.enabled" = "yes"; 20 } 21 ) 22 ), 23 extraAVDFiles ? [ ], 24 package ? null, 25 activity ? null, 26 androidUserHome ? null, 27 avdHomeDir ? null, # Support old variable with non-standard naming! 28 androidAvdHome ? avdHomeDir, 29 deviceName ? "device", 30 sdkExtraArgs ? { }, 31 androidAvdFlags ? null, 32 androidEmulatorFlags ? null, 33}: 34 35let 36 sdkArgs = { 37 includeEmulator = true; 38 includeSystemImages = true; 39 } 40 // sdkExtraArgs 41 // { 42 cmdLineToolsVersion = "8.0"; 43 platformVersions = [ platformVersion ]; 44 systemImageTypes = [ systemImageType ]; 45 abiVersions = [ abiVersion ]; 46 }; 47 48 sdk = (composeAndroidPackages sdkArgs).androidsdk; 49in 50stdenv.mkDerivation { 51 inherit name; 52 53 buildCommand = '' 54 mkdir -p $out/bin 55 56 cat > $out/bin/run-test-emulator << "EOF" 57 #!${runtimeShell} -e 58 59 # We need a TMPDIR 60 if [ "$TMPDIR" = "" ] 61 then 62 export TMPDIR=/tmp 63 fi 64 65 ${ 66 if androidUserHome == null then 67 '' 68 # Store the virtual devices somewhere else, instead of polluting a user's HOME directory 69 export ANDROID_USER_HOME=$(mktemp -d $TMPDIR/nix-android-user-home-XXXX) 70 '' 71 else 72 '' 73 mkdir -p "${androidUserHome}" 74 export ANDROID_USER_HOME="${androidUserHome}" 75 '' 76 } 77 78 ${ 79 if androidAvdHome == null then 80 '' 81 export ANDROID_AVD_HOME=$ANDROID_USER_HOME/avd 82 '' 83 else 84 '' 85 mkdir -p "${androidAvdHome}" 86 export ANDROID_AVD_HOME="${androidAvdHome}" 87 '' 88 } 89 90 # We need to specify the location of the Android SDK root folder 91 export ANDROID_SDK_ROOT=${sdk}/libexec/android-sdk 92 93 ${lib.optionalString (androidAvdFlags != null) '' 94 # If NIX_ANDROID_AVD_FLAGS is empty 95 if [[ -z "$NIX_ANDROID_AVD_FLAGS" ]]; then 96 NIX_ANDROID_AVD_FLAGS="${androidAvdFlags}" 97 fi 98 ''} 99 100 ${lib.optionalString (androidEmulatorFlags != null) '' 101 # If NIX_ANDROID_EMULATOR_FLAGS is empty 102 if [[ -z "$NIX_ANDROID_EMULATOR_FLAGS" ]]; then 103 NIX_ANDROID_EMULATOR_FLAGS="${androidEmulatorFlags}" 104 fi 105 ''} 106 107 # We have to look for a free TCP port 108 109 echo "Looking for a free TCP port in range 5554-5584" >&2 110 111 for i in $(seq 5554 2 5584) 112 do 113 if [ -z "$(${sdk}/bin/adb devices | grep emulator-$i)" ] 114 then 115 port=$i 116 break 117 fi 118 done 119 120 if [ -z "$port" ] 121 then 122 echo "Unfortunately, the emulator port space is exhausted!" >&2 123 exit 1 124 else 125 echo "We have a free TCP port: $port" >&2 126 fi 127 128 export ANDROID_SERIAL="emulator-$port" 129 130 # Create a virtual android device for testing if it does not exist 131 if [ "$(${sdk}/bin/avdmanager list avd | grep 'Name: ${deviceName}')" = "" ] 132 then 133 # Create a virtual android device 134 yes "" | ${sdk}/bin/avdmanager create avd --force -n ${deviceName} -k "system-images;android-${platformVersion};${systemImageType};${abiVersion}" -p $ANDROID_AVD_HOME/${deviceName}.avd $NIX_ANDROID_AVD_FLAGS 135 136 ${builtins.concatStringsSep "\n" ( 137 lib.mapAttrsToList (configKey: configValue: '' 138 echo "${configKey} = ${configValue}" >> $ANDROID_AVD_HOME/${deviceName}.avd/config.ini 139 '') configOptions 140 )} 141 142 ${lib.concatMapStrings (extraAVDFile: '' 143 ln -sf ${extraAVDFile} $ANDROID_AVD_HOME/${deviceName}.avd 144 '') extraAVDFiles} 145 fi 146 147 # Launch the emulator 148 echo "\nLaunch the emulator" 149 $ANDROID_SDK_ROOT/emulator/emulator -avd ${deviceName} -no-boot-anim -port $port $NIX_ANDROID_EMULATOR_FLAGS & 150 151 # Wait until the device has completely booted 152 echo "Waiting until the emulator has booted the ${deviceName} and the package manager is ready..." >&2 153 154 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port wait-for-device 155 156 echo "Device state has been reached" >&2 157 158 while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop dev.bootcomplete | grep 1)" ] 159 do 160 sleep 5 161 done 162 163 echo "dev.bootcomplete property is 1" >&2 164 165 #while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop sys.boot_completed | grep 1)" ] 166 #do 167 #sleep 5 168 #done 169 170 #echo "sys.boot_completed property is 1" >&2 171 172 echo "ready" >&2 173 174 ${lib.optionalString (app != null) '' 175 # Install the App through the debugger, if it has not been installed yet 176 177 if [ -z "${package}" ] || [ "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell pm list packages | grep package:${package})" = "" ] 178 then 179 if [ -d "${app}" ] 180 then 181 appPath="$(echo ${app}/*.apk)" 182 else 183 appPath="${app}" 184 fi 185 186 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port install "$appPath" 187 fi 188 189 # Start the application 190 ${lib.optionalString (package != null && activity != null) '' 191 ${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell am start -a android.intent.action.MAIN -n ${package}/${activity} 192 ''} 193 ''} 194 EOF 195 chmod +x $out/bin/run-test-emulator 196 ''; 197 198 meta = meta // { 199 mainProgram = "run-test-emulator"; 200 }; 201}