nixpkgs/pkgs/development/mobile/androidenv/compose-android-packages.nix
Morgan Jones a7e5b070be androidenv: Allow multiple ndkVersions to be specified
Android is deprecating ndk.dir in favor of specifying exact NDK
version in Gradle configuration. Ensure that we can support multiple
NDKs, and link them into the location the Android Gradle Plugin expects.
2021-04-24 15:56:08 -06:00

321 lines
11 KiB
Nix

{ requireFile, autoPatchelfHook, pkgs, pkgsHostHost, pkgs_i686
, licenseAccepted ? false
}:
{ toolsVersion ? "26.1.1"
, platformToolsVersion ? "31.0.2"
, buildToolsVersions ? [ "30.0.3" ]
, includeEmulator ? false
, emulatorVersion ? "30.6.3"
, platformVersions ? []
, includeSources ? false
, includeSystemImages ? false
, systemImageTypes ? [ "google_apis_playstore" ]
, abiVersions ? [ "armeabi-v7a" ]
, cmakeVersions ? [ ]
, includeNDK ? false
, ndkVersion ? "22.1.7171670"
, ndkVersions ? [ndkVersion]
, useGoogleAPIs ? false
, useGoogleTVAddOns ? false
, includeExtras ? []
, repoJson ? ./repo.json
, repoXmls ? null
, extraLicenses ? []
}:
let
inherit (pkgs) stdenv lib fetchurl;
inherit (pkgs.buildPackages) makeWrapper unzip;
# Determine the Android os identifier from Nix's system identifier
os = if stdenv.system == "x86_64-linux" then "linux"
else if stdenv.system == "x86_64-darwin" then "macosx"
else throw "No Android SDK tarballs are available for system architecture: ${stdenv.system}";
# Uses mkrepo.rb to create a repo spec.
mkRepoJson = { packages ? [], images ? [], addons ? [] }: let
mkRepoRuby = (pkgs.ruby.withPackages (pkgs: with pkgs; [ slop nokogiri ]));
mkRepoRubyArguments = lib.lists.flatten [
(builtins.map (package: ["--packages" "${package}"]) packages)
(builtins.map (image: ["--images" "${image}"]) images)
(builtins.map (addon: ["--addons" "${addon}"]) addons)
];
in
stdenv.mkDerivation {
name = "androidenv-repo-json";
buildInputs = [ mkRepoRuby ];
preferLocalBuild = true;
unpackPhase = "true";
buildPhase = ''
ruby ${./mkrepo.rb} ${lib.escapeShellArgs mkRepoRubyArguments} > repo.json
'';
installPhase = ''
mv repo.json $out
'';
};
# Reads the repo JSON. If repoXmls is provided, will build a repo JSON into the Nix store.
repo = if repoXmls != null then
let
repoXmlSpec = {
packages = repoXmls.packages or [];
images = repoXmls.images or [];
addons = repoXmls.addons or [];
};
in
builtins.fromJSON (builtins.readFile "${mkRepoJson repoXmlSpec}")
else
builtins.fromJSON (builtins.readFile repoJson);
# Converts all 'archives' keys in a repo spec to fetchurl calls.
fetchArchives = attrSet:
lib.attrsets.mapAttrsRecursive
(path: value:
if (builtins.elemAt path ((builtins.length path) - 1)) == "archives" then
(builtins.listToAttrs
(builtins.map
(archive: lib.attrsets.nameValuePair archive.os (fetchurl { inherit (archive) url sha1; })) value))
else value
)
attrSet;
# Converts the repo attrset into fetch calls
packages = fetchArchives repo.packages;
system-images-packages = fetchArchives repo.images;
addons = {
addons = fetchArchives repo.addons;
extras = fetchArchives repo.extras;
};
# Converts a license name to a list of license texts.
mkLicenses = licenseName: repo.licenses.${licenseName};
# Converts a list of license names to a flattened list of license texts.
# Just used for displaying licenses.
mkLicenseTexts = licenseNames:
lib.lists.flatten
(builtins.map
(licenseName:
builtins.map
(licenseText: "--- ${licenseName} ---\n${licenseText}")
(mkLicenses licenseName))
licenseNames);
# Converts a license name to a list of license hashes.
mkLicenseHashes = licenseName:
builtins.map
(licenseText: builtins.hashString "sha1" licenseText)
(mkLicenses licenseName);
# The list of all license names we're accepting. Put android-sdk-license there
# by default.
licenseNames = lib.lists.unique ([
"android-sdk-license"
] ++ extraLicenses);
in
rec {
deployAndroidPackage = import ./deploy-androidpackage.nix {
inherit stdenv unzip;
};
platform-tools = import ./platform-tools.nix {
inherit deployAndroidPackage os autoPatchelfHook pkgs lib;
package = packages.platform-tools.${platformToolsVersion};
};
build-tools = map (version:
import ./build-tools.nix {
inherit deployAndroidPackage os autoPatchelfHook makeWrapper pkgs pkgs_i686 lib;
package = packages.build-tools.${version};
}
) buildToolsVersions;
emulator = import ./emulator.nix {
inherit deployAndroidPackage os autoPatchelfHook makeWrapper pkgs pkgs_i686 lib;
package = packages.emulator.${emulatorVersion};
};
platforms = map (version:
deployAndroidPackage {
inherit os;
package = packages.platforms.${version};
}
) platformVersions;
sources = map (version:
deployAndroidPackage {
inherit os;
package = packages.sources.${version};
}
) platformVersions;
system-images = lib.flatten (map (apiVersion:
map (type:
map (abiVersion:
if lib.hasAttrByPath [apiVersion type abiVersion] system-images-packages then
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) ''
sed -i '/^Addon.Vendor/d' source.properties
'';
}
else []
) abiVersions
) systemImageTypes
) platformVersions);
cmake = map (version:
import ./cmake.nix {
inherit deployAndroidPackage os autoPatchelfHook pkgs lib;
package = packages.cmake.${version};
}
) cmakeVersions;
# Creates a NDK bundle.
makeNdkBundle = ndkVersion:
import ./ndk-bundle {
inherit deployAndroidPackage os autoPatchelfHook makeWrapper pkgs pkgsHostHost lib platform-tools;
package = packages.ndk-bundle.${ndkVersion};
};
# All NDK bundles.
ndk-bundles = if includeNDK then map makeNdkBundle ndkVersions else [];
# The "default" NDK bundle.
ndk-bundle = if includeNDK then lib.findFirst (x: x != null) null ndk-bundles else null;
google-apis = map (version:
deployAndroidPackage {
inherit os;
package = addons.addons.${version}.google_apis;
}
) (builtins.filter (platformVersion: platformVersion < "26") platformVersions); # API level 26 and higher include Google APIs by default
google-tv-addons = map (version:
deployAndroidPackage {
inherit os;
package = addons.addons.${version}.google_tv_addon;
}
) platformVersions;
# Function that automatically links all plugins for which multiple versions can coexist
linkPlugins = {name, plugins}:
lib.optionalString (plugins != []) ''
mkdir -p ${name}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
'') plugins}
'';
# Function that automatically links all NDK plugins.
linkNdkPlugins = {name, plugins, rootName ? name}:
lib.optionalString (plugins != []) ''
mkdir -p ${rootName}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name} ${rootName}/${plugin.version}
'') plugins}
'';
# Function that automatically links a plugin for which only one version exists
linkPlugin = {name, plugin, check ? true}:
lib.optionalString check ''
ln -s ${plugin}/libexec/android-sdk/* ${name}
'';
# Links all plugins related to a requested platform
linkPlatformPlugins = {name, plugins, check}:
lib.optionalString check ''
mkdir -p ${name}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
'') plugins}
''; # */
# This derivation deploys the tools package and symlinks all the desired
# plugins that we want to use. If the license isn't accepted, prints all the licenses
# requested and throws.
androidsdk = if !licenseAccepted then throw ''
${builtins.concatStringsSep "\n\n" (mkLicenseTexts licenseNames)}
You must accept the following licenses:
${lib.concatMapStringsSep "\n" (str: " - ${str}") licenseNames}
by setting nixpkgs config option 'android_sdk.accept_license = true;'.
'' else import ./tools.nix {
inherit deployAndroidPackage requireFile packages toolsVersion autoPatchelfHook makeWrapper os pkgs pkgs_i686 lib;
postInstall = ''
# Symlink all requested plugins
${linkPlugin { name = "platform-tools"; plugin = platform-tools; }}
${linkPlugins { name = "build-tools"; plugins = build-tools; }}
${linkPlugin { name = "emulator"; plugin = emulator; check = includeEmulator; }}
${linkPlugins { name = "platforms"; plugins = platforms; }}
${linkPlatformPlugins { name = "sources"; plugins = sources; check = includeSources; }}
${linkPlugins { name = "cmake"; plugins = cmake; }}
${linkNdkPlugins { name = "ndk-bundle"; rootName = "ndk"; plugins = ndk-bundles; }}
${linkPlugin { name = "ndk-bundle"; plugin = ndk-bundle; check = includeNDK; }}
${lib.optionalString includeSystemImages ''
mkdir -p system-images
${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
'') system-images}
''}
${linkPlatformPlugins { name = "add-ons"; plugins = google-apis; check = useGoogleAPIs; }}
${linkPlatformPlugins { name = "add-ons"; plugins = google-apis; check = useGoogleTVAddOns; }}
# Link extras
${lib.concatMapStrings (identifier:
let
path = addons.extras.${identifier}.path;
addon = deployAndroidPackage {
inherit os;
package = addons.extras.${identifier};
};
in
''
targetDir=$(dirname ${path})
mkdir -p $targetDir
ln -s ${addon}/libexec/android-sdk/${path} $targetDir
'') includeExtras}
# Expose common executables in bin/
mkdir -p $out/bin
find $PWD/tools -not -path '*/\.*' -type f -executable -mindepth 1 -maxdepth 1 | while read i
do
ln -s $i $out/bin
done
find $PWD/tools/bin -not -path '*/\.*' -type f -executable -mindepth 1 -maxdepth 1 | while read i
do
ln -s $i $out/bin
done
for i in ${platform-tools}/bin/*
do
ln -s $i $out/bin
done
# Write licenses
mkdir -p licenses
${lib.concatMapStrings (licenseName:
let
licenseHashes = builtins.concatStringsSep "\n" (mkLicenseHashes licenseName);
licenseHashFile = pkgs.writeText "androidenv-${licenseName}" licenseHashes;
in
''
ln -s ${licenseHashFile} licenses/${licenseName}
'') licenseNames}
'';
};
}