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

androidenv: Fix avdmanager create avd #213871

Merged
merged 3 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions pkgs/development/mobile/androidenv/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/xml
local.properties
.android
52 changes: 37 additions & 15 deletions pkgs/development/mobile/androidenv/compose-android-packages.nix
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,38 @@ rec {

system-images = lib.flatten (map (apiVersion:
map (type:
map (abiVersion:
lib.optionals (lib.hasAttrByPath [apiVersion type abiVersion] system-images-packages) (
deployAndroidPackage {
inherit os;
package = system-images-packages.${apiVersion}.${type}.${abiVersion};
# Patch 'google_apis' system images so they're recognized by the sdk.
# Without this, `android list targets` shows 'Tag/ABIs : no ABIs' instead
# of 'Tag/ABIs : google_apis*/*' and the emulator fails with an ABI-related error.
patchInstructions = lib.optionalString (lib.hasPrefix "google_apis" type) ''
# Deploy all system images with the same systemImageType in one derivation to avoid the `null` problem below
# with avdmanager when trying to create an avd!
#
# ```
# $ yes "" | avdmanager create avd --force --name testAVD --package 'system-images;android-33;google_apis;x86_64'
# Error: Package path is not valid. Valid system image paths are:
# null
# ```
let
availablePackages = map (abiVersion:
system-images-packages.${apiVersion}.${type}.${abiVersion}
) (builtins.filter (abiVersion:
lib.hasAttrByPath [apiVersion type abiVersion] system-images-packages
) abiVersions);

instructions = builtins.listToAttrs (map (package: {
name = package.name;
value = lib.optionalString (lib.hasPrefix "google_apis" type) ''
# Patch 'google_apis' system images so they're recognized by the sdk.
# Without this, `android list targets` shows 'Tag/ABIs : no ABIs' instead
# of 'Tag/ABIs : google_apis*/*' and the emulator fails with an ABI-related error.
sed -i '/^Addon.Vendor/d' source.properties
'';
}
)
) abiVersions
}) availablePackages
);
in
lib.optionals (availablePackages != [])
(deployAndroidPackages {
inherit os;
packages = availablePackages;
patchesInstructions = instructions;
})
) systemImageTypes
) platformVersions);

Expand Down Expand Up @@ -271,8 +289,8 @@ rec {
${lib.concatMapStrings (system-image: ''
apiVersion=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*))
type=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*/*))
mkdir -p system-images/$apiVersion/$type
ln -s ${system-image}/libexec/android-sdk/system-images/$apiVersion/$type/* system-images/$apiVersion/$type
mkdir -p system-images/$apiVersion
ln -s ${system-image}/libexec/android-sdk/system-images/$apiVersion/$type system-images/$apiVersion/$type
'') images}
'';

Expand All @@ -294,7 +312,11 @@ rec {
You must accept the following licenses:
${lib.concatMapStringsSep "\n" (str: " - ${str}") licenseNames}

by setting nixpkgs config option 'android_sdk.accept_license = true;'.
a)
by setting nixpkgs config option 'android_sdk.accept_license = true;'.
b)
by an environment variable for a single invocation of the nix tools.
$ export NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1
'' else callPackage ./cmdline-tools.nix {
inherit deployAndroidPackage os cmdLineToolsVersion;

Expand Down
2 changes: 1 addition & 1 deletion pkgs/development/mobile/androidenv/default.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{ config, pkgs ? import <nixpkgs> {}
, licenseAccepted ? config.android_sdk.accept_license or false
, licenseAccepted ? config.android_sdk.accept_license or (builtins.getEnv "NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE" == "1")
}:

rec {
Expand Down
68 changes: 50 additions & 18 deletions pkgs/development/mobile/androidenv/emulate-app.nix
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
{ composeAndroidPackages, stdenv, lib, runtimeShell }:
{ name, app ? null
, platformVersion ? "16", abiVersion ? "armeabi-v7a", systemImageType ? "default"
, enableGPU ? false, extraAVDFiles ? []
, package ? null, activity ? null
, avdHomeDir ? null, sdkExtraArgs ? {}
, platformVersion ? "33"
, abiVersion ? "armeabi-v7a"
, systemImageType ? "default"
, enableGPU ? false
, extraAVDFiles ? []
, package ? null
, activity ? null
, androidUserHome ? null
, avdHomeDir ? null # Support old variable with non-standard naming!
, androidAvdHome ? avdHomeDir
, sdkExtraArgs ? {}
, androidAvdFlags ? null
, androidEmulatorFlags ? null
}:

let
sdkArgs = {
toolsVersion = "26.1.1";
platformVersions = [ platformVersion ];
includeEmulator = true;
includeSystemImages = true;
} // sdkExtraArgs // {
cmdLineToolsVersion = "8.0";
platformVersions = [ platformVersion ];
systemImageTypes = [ systemImageType ];
abiVersions = [ abiVersion ];
} // sdkExtraArgs;
};

sdk = (composeAndroidPackages sdkArgs).androidsdk;
in
Expand All @@ -33,24 +43,45 @@ stdenv.mkDerivation {
export TMPDIR=/tmp
fi

${if avdHomeDir == null then ''
${if androidUserHome == null then ''
# Store the virtual devices somewhere else, instead of polluting a user's HOME directory
export ANDROID_SDK_HOME=$(mktemp -d $TMPDIR/nix-android-vm-XXXX)
export ANDROID_USER_HOME=$(mktemp -d $TMPDIR/nix-android-user-home-XXXX)
'' else ''
mkdir -p "${androidUserHome}"
export ANDROID_USER_HOME="${androidUserHome}"
''}

${if androidAvdHome == null then ''
export ANDROID_AVD_HOME=$ANDROID_USER_HOME/avd
'' else ''
mkdir -p "${avdHomeDir}"
export ANDROID_SDK_HOME="${avdHomeDir}"
mkdir -p "${androidAvdHome}"
export ANDROID_AVD_HOME="${androidAvdHome}"
''}

# We need to specify the location of the Android SDK root folder
export ANDROID_SDK_ROOT=${sdk}/libexec/android-sdk

${lib.optionalString (androidAvdFlags != null) ''
# If NIX_ANDROID_AVD_FLAGS is empty
if [[ -z "$NIX_ANDROID_AVD_FLAGS" ]]; then
NIX_ANDROID_AVD_FLAGS="${androidAvdFlags}"
fi
''}

${lib.optionalString (androidEmulatorFlags != null) ''
# If NIX_ANDROID_EMULATOR_FLAGS is empty
if [[ -z "$NIX_ANDROID_EMULATOR_FLAGS" ]]; then
NIX_ANDROID_EMULATOR_FLAGS="${androidEmulatorFlags}"
fi
''}

# We have to look for a free TCP port

echo "Looking for a free TCP port in range 5554-5584" >&2

for i in $(seq 5554 2 5584)
do
if [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb devices | grep emulator-$i)" ]
if [ -z "$(${sdk}/bin/adb devices | grep emulator-$i)" ]
then
port=$i
break
Expand All @@ -68,25 +99,26 @@ stdenv.mkDerivation {
export ANDROID_SERIAL="emulator-$port"

# Create a virtual android device for testing if it does not exist
${sdk}/libexec/android-sdk/tools/bin/avdmanager list target
${sdk}/bin/avdmanager list target

if [ "$(${sdk}/libexec/android-sdk/tools/android list avd | grep 'Name: device')" = "" ]
if [ "$(${sdk}/bin/avdmanager list avd | grep 'Name: device')" = "" ]
then
# Create a virtual android device
yes "" | ${sdk}/libexec/android-sdk/tools/bin/avdmanager create avd -n device -k "system-images;android-${platformVersion};${systemImageType};${abiVersion}" $NIX_ANDROID_AVD_FLAGS
yes "" | ${sdk}/bin/avdmanager create avd --force -n device -k "system-images;android-${platformVersion};${systemImageType};${abiVersion}" -p $ANDROID_AVD_HOME $NIX_ANDROID_AVD_FLAGS

${lib.optionalString enableGPU ''
# Enable GPU acceleration
echo "hw.gpu.enabled=yes" >> $ANDROID_SDK_HOME/.android/avd/device.avd/config.ini
echo "hw.gpu.enabled=yes" >> $ANDROID_AVD_HOME/device.avd/config.ini
''}

${lib.concatMapStrings (extraAVDFile: ''
ln -sf ${extraAVDFile} $ANDROID_SDK_HOME/.android/avd/device.avd
ln -sf ${extraAVDFile} $ANDROID_AVD_HOME/device.avd
'') extraAVDFiles}
fi

# Launch the emulator
${sdk}/libexec/android-sdk/emulator/emulator -avd device -no-boot-anim -port $port $NIX_ANDROID_EMULATOR_FLAGS &
echo "\nLaunch the emulator"
$ANDROID_SDK_ROOT/emulator/emulator -avd device -no-boot-anim -port $port $NIX_ANDROID_EMULATOR_FLAGS &

# Wait until the device has completely booted
echo "Waiting until the emulator has booted the device and the package manager is ready..." >&2
Expand Down
151 changes: 151 additions & 0 deletions pkgs/development/mobile/androidenv/examples/shell-with-emulator.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
# To test your changes in androidEnv run `nix-shell android-sdk-with-emulator-shell.nix`

# If you copy this example out of nixpkgs, use these lines instead of the next.
# This example pins nixpkgs: https://nix.dev/tutorials/towards-reproducibility-pinning-nixpkgs.html
/*nixpkgsSource ? (builtins.fetchTarball {
name = "nixpkgs-20.09";
url = "https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz";
sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy";
}),
pkgs ? import nixpkgsSource {
config.allowUnfree = true;
},
*/

# If you want to use the in-tree version of nixpkgs:
pkgs ? import ../../../../.. {
config.allowUnfree = true;
hadilq marked this conversation as resolved.
Show resolved Hide resolved
},

config ? pkgs.config
}:

# Copy this file to your Android project.
let
# Declaration of versions for everything. This is useful since these
# versions may be used in multiple places in this Nix expression.
android = {
platforms = [ "33" ];
systemImageTypes = [ "google_apis" ];
abis = [ "arm64-v8a" "x86_64" ];
};

# If you copy this example out of nixpkgs, something like this will work:
/*androidEnvNixpkgs = fetchTarball {
name = "androidenv";
url = "https://github.com/NixOS/nixpkgs/archive/<fill me in from Git>.tar.gz";
sha256 = "<fill me in with nix-prefetch-url --unpack>";
};

androidEnv = pkgs.callPackage "${androidEnvNixpkgs}/pkgs/development/mobile/androidenv" {
inherit config pkgs;
licenseAccepted = true;
};*/

# Otherwise, just use the in-tree androidenv:
androidEnv = pkgs.callPackage ./.. {
inherit config pkgs;
# You probably need to uncomment below line to express consent.
# licenseAccepted = true;
};

sdkArgs = {
platformVersions = android.platforms;
abiVersions = android.abis;
systemImageTypes = android.systemImageTypes;

includeSystemImages = true;
includeEmulator = true;

# Accepting more licenses declaratively:
extraLicenses = [
# Already accepted for you with the global accept_license = true or
# licenseAccepted = true on androidenv.
# "android-sdk-license"

# These aren't, but are useful for more uncommon setups.
"android-sdk-preview-license"
"android-googletv-license"
"android-sdk-arm-dbt-license"
"google-gdk-license"
"intel-android-extra-license"
"intel-android-sysimage-license"
"mips-android-sysimage-license"
];
};

androidComposition = androidEnv.composeAndroidPackages sdkArgs;
androidEmulator = androidEnv.emulateApp {
name = "android-sdk-emulator-demo";
sdkExtraArgs = sdkArgs;
};
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
jdk = pkgs.jdk;
in
pkgs.mkShell rec {
name = "androidenv-demo";
packages = [ androidSdk platformTools androidEmulator jdk pkgs.android-studio ];

LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = jdk.home;

# Note: ANDROID_HOME is deprecated. Use ANDROID_SDK_ROOT.
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";

shellHook = ''
# Write out local.properties for Android Studio.
cat <<EOF > local.properties
# This file was automatically generated by nix-shell.
sdk.dir=$ANDROID_SDK_ROOT
ndk.dir=$ANDROID_NDK_ROOT
EOF
'';

passthru.tests = {

shell-with-emulator-sdkmanager-packages-test = pkgs.runCommand "shell-with-emulator-sdkmanager-packages-test" {
nativeBuildInputs = [ androidSdk jdk ];
} ''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
echo "installed_packages_section: ''${installed_packages_section}"

packages=(
"build-tools;33.0.1" "cmdline-tools;8.0" \
"emulator" "patcher;v4" "platform-tools" "platforms;android-33" \
"system-images;android-33;google_apis;arm64-v8a" \
"system-images;android-33;google_apis;x86_64"
)

for package in "''${packages[@]}"; do
if [[ ! $installed_packages_section =~ "$package" ]]; then
echo "$package package was not installed."
exit 1
fi
done

touch "$out"
'';

shell-with-emulator-avdmanager-create-avd-test = pkgs.runCommand "shell-with-emulator-avdmanager-create-avd-test" {
nativeBuildInputs = [ androidSdk androidEmulator jdk ];
} ''
avdmanager delete avd -n testAVD || true
echo "" | avdmanager create avd --force --name testAVD --package 'system-images;android-33;google_apis;x86_64'
result=$(avdmanager list avd)

if [[ ! $result =~ "Name: testAVD" ]]; then
echo "avdmanager couldn't create the avd! The output is :''${result}"
exit 1
fi

avdmanager delete avd -n testAVD || true
touch "$out"
'';
};
}

14 changes: 8 additions & 6 deletions pkgs/development/mobile/androidenv/examples/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ let
# Otherwise, just use the in-tree androidenv:
androidEnv = pkgs.callPackage ./.. {
inherit config pkgs;
licenseAccepted = true;
# You probably need to uncomment below line to express consent.
# licenseAccepted = true;
};

androidComposition = androidEnv.composeAndroidPackages {
Expand Down Expand Up @@ -146,8 +147,9 @@ pkgs.mkShell rec {
'';

passthru.tests = {
sdkmanager-licenses-test = pkgs.runCommand "sdkmanager-licenses-test" {
buildInputs = [ androidSdk jdk ];

shell-sdkmanager-licenses-test = pkgs.runCommand "shell-sdkmanager-licenses-test" {
nativeBuildInputs = [ androidSdk jdk ];
} ''
if [[ ! "$(sdkmanager --licenses)" =~ "All SDK package licenses accepted." ]]; then
echo "At least one of SDK package licenses are not accepted."
Expand All @@ -156,14 +158,14 @@ pkgs.mkShell rec {
touch $out
'';

sdkmanager-packages-test = pkgs.runCommand "sdkmanager-packages-test" {
buildInputs = [ androidSdk jdk ];
shell-sdkmanager-packages-test = pkgs.runCommand "shell-sdkmanager-packages-test" {
nativeBuildInputs = [ androidSdk jdk ];
} ''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')

packages=(
"build-tools;30.0.3" "ndk-bundle" "platform-tools" \
"build-tools;30.0.3" "platform-tools" \
"platforms;android-23" "platforms;android-24" "platforms;android-25" "platforms;android-26" \
"platforms;android-27" "platforms;android-28" "platforms;android-29" "platforms;android-30" \
"platforms;android-31" "platforms;android-32" "platforms;android-33" \
Expand Down
Loading