nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{ callPackage, stdenv, lib, fetchurl, ruby, writeText
2, licenseAccepted ? false
3}:
4
5{ cmdLineToolsVersion ? "9.0"
6, toolsVersion ? "26.1.1"
7, platformToolsVersion ? "34.0.1"
8, buildToolsVersions ? [ "33.0.2" ]
9, includeEmulator ? false
10, emulatorVersion ? "33.1.6"
11, platformVersions ? []
12, includeSources ? false
13, includeSystemImages ? false
14, systemImageTypes ? [ "google_apis_playstore" ]
15, abiVersions ? [ "armeabi-v7a" "arm64-v8a" ]
16, cmakeVersions ? [ ]
17, includeNDK ? false
18, ndkVersion ? "25.2.9519653"
19, ndkVersions ? [ndkVersion]
20, useGoogleAPIs ? false
21, useGoogleTVAddOns ? false
22, includeExtras ? []
23, repoJson ? ./repo.json
24, repoXmls ? null
25, extraLicenses ? []
26}:
27
28let
29 # Determine the Android os identifier from Nix's system identifier
30 os = if stdenv.system == "x86_64-linux" then "linux"
31 else if stdenv.system == "x86_64-darwin" then "macosx"
32 else throw "No Android SDK tarballs are available for system architecture: ${stdenv.system}";
33
34 # Uses mkrepo.rb to create a repo spec.
35 mkRepoJson = { packages ? [], images ? [], addons ? [] }: let
36 mkRepoRuby = (ruby.withPackages (pkgs: with pkgs; [ slop nokogiri ]));
37 mkRepoRubyArguments = lib.lists.flatten [
38 (builtins.map (package: ["--packages" "${package}"]) packages)
39 (builtins.map (image: ["--images" "${image}"]) images)
40 (builtins.map (addon: ["--addons" "${addon}"]) addons)
41 ];
42 in
43 stdenv.mkDerivation {
44 name = "androidenv-repo-json";
45 buildInputs = [ mkRepoRuby ];
46 preferLocalBuild = true;
47 unpackPhase = "true";
48 buildPhase = ''
49 ruby ${./mkrepo.rb} ${lib.escapeShellArgs mkRepoRubyArguments} > repo.json
50 '';
51 installPhase = ''
52 mv repo.json $out
53 '';
54 };
55
56 # Reads the repo JSON. If repoXmls is provided, will build a repo JSON into the Nix store.
57 repo = if repoXmls != null then
58 let
59 repoXmlSpec = {
60 packages = repoXmls.packages or [];
61 images = repoXmls.images or [];
62 addons = repoXmls.addons or [];
63 };
64 in
65 lib.importJSON "${mkRepoJson repoXmlSpec}"
66 else
67 lib.importJSON repoJson;
68
69 # Converts all 'archives' keys in a repo spec to fetchurl calls.
70 fetchArchives = attrSet:
71 lib.attrsets.mapAttrsRecursive
72 (path: value:
73 if (builtins.elemAt path ((builtins.length path) - 1)) == "archives" then
74 (builtins.listToAttrs
75 (builtins.map
76 (archive: lib.attrsets.nameValuePair archive.os (fetchurl { inherit (archive) url sha1; })) value))
77 else value
78 )
79 attrSet;
80
81 # Converts the repo attrset into fetch calls
82 packages = fetchArchives repo.packages;
83 system-images-packages = fetchArchives repo.images;
84 addons = {
85 addons = fetchArchives repo.addons;
86 extras = fetchArchives repo.extras;
87 };
88
89 # Converts a license name to a list of license texts.
90 mkLicenses = licenseName: repo.licenses.${licenseName};
91
92 # Converts a list of license names to a flattened list of license texts.
93 # Just used for displaying licenses.
94 mkLicenseTexts = licenseNames:
95 lib.lists.flatten
96 (builtins.map
97 (licenseName:
98 builtins.map
99 (licenseText: "--- ${licenseName} ---\n${licenseText}")
100 (mkLicenses licenseName))
101 licenseNames);
102
103 # Converts a license name to a list of license hashes.
104 mkLicenseHashes = licenseName:
105 builtins.map
106 (licenseText: builtins.hashString "sha1" licenseText)
107 (mkLicenses licenseName);
108
109 # The list of all license names we're accepting. Put android-sdk-license there
110 # by default.
111 licenseNames = lib.lists.unique ([
112 "android-sdk-license"
113 ] ++ extraLicenses);
114in
115rec {
116 deployAndroidPackages = callPackage ./deploy-androidpackages.nix {
117 inherit stdenv lib mkLicenses;
118 };
119
120 deployAndroidPackage = ({package, os ? null, buildInputs ? [], patchInstructions ? "", meta ? {}, ...}@args:
121 let
122 extraParams = removeAttrs args [ "package" "os" "buildInputs" "patchInstructions" ];
123 in
124 deployAndroidPackages ({
125 inherit os buildInputs meta;
126 packages = [ package ];
127 patchesInstructions = { "${package.name}" = patchInstructions; };
128 } // extraParams
129 ));
130
131 # put a much nicer error message that includes the available options.
132 check-version = packages: package: version:
133 if lib.hasAttrByPath [ package version ] packages then
134 packages.${package}.${version}
135 else
136 throw ''
137 The version ${version} is missing in package ${package}.
138 The only available versions are ${builtins.concatStringsSep ", " (builtins.attrNames packages.${package})}.
139 '';
140
141 platform-tools = callPackage ./platform-tools.nix {
142 inherit deployAndroidPackage;
143 os = if stdenv.system == "aarch64-darwin" then "macosx" else os; # "macosx" is a universal binary here
144 package = check-version packages "platform-tools" platformToolsVersion;
145 };
146
147 tools = callPackage ./tools.nix {
148 inherit deployAndroidPackage os;
149 package = check-version packages "tools" toolsVersion;
150
151 postInstall = ''
152 ${linkPlugin { name = "platform-tools"; plugin = platform-tools; }}
153 ${linkPlugin { name = "patcher"; plugin = patcher; }}
154 ${linkPlugin { name = "emulator"; plugin = emulator; }}
155 '';
156 };
157
158 patcher = callPackage ./patcher.nix {
159 inherit deployAndroidPackage os;
160 package = packages.patcher."1";
161 };
162
163 build-tools = map (version:
164 callPackage ./build-tools.nix {
165 inherit deployAndroidPackage os;
166 package = check-version packages "build-tools" version;
167
168 postInstall = ''
169 ${linkPlugin { name = "tools"; plugin = tools; check = toolsVersion != null; }}
170 '';
171 }
172 ) buildToolsVersions;
173
174 emulator = callPackage ./emulator.nix {
175 inherit deployAndroidPackage os;
176 package = check-version packages "emulator" emulatorVersion;
177
178 postInstall = ''
179 ${linkSystemImages { images = system-images; check = includeSystemImages; }}
180 '';
181 };
182
183 platforms = map (version:
184 deployAndroidPackage {
185 inherit os;
186 package = check-version packages "platforms" version;
187 }
188 ) platformVersions;
189
190 sources = map (version:
191 deployAndroidPackage {
192 inherit os;
193 package = check-version packages "sources" version;
194 }
195 ) platformVersions;
196
197 system-images = lib.flatten (map (apiVersion:
198 map (type:
199 # Deploy all system images with the same systemImageType in one derivation to avoid the `null` problem below
200 # with avdmanager when trying to create an avd!
201 #
202 # ```
203 # $ yes "" | avdmanager create avd --force --name testAVD --package 'system-images;android-33;google_apis;x86_64'
204 # Error: Package path is not valid. Valid system image paths are:
205 # null
206 # ```
207 let
208 availablePackages = map (abiVersion:
209 system-images-packages.${apiVersion}.${type}.${abiVersion}
210 ) (builtins.filter (abiVersion:
211 lib.hasAttrByPath [apiVersion type abiVersion] system-images-packages
212 ) abiVersions);
213
214 instructions = builtins.listToAttrs (map (package: {
215 name = package.name;
216 value = lib.optionalString (lib.hasPrefix "google_apis" type) ''
217 # Patch 'google_apis' system images so they're recognized by the sdk.
218 # Without this, `android list targets` shows 'Tag/ABIs : no ABIs' instead
219 # of 'Tag/ABIs : google_apis*/*' and the emulator fails with an ABI-related error.
220 sed -i '/^Addon.Vendor/d' source.properties
221 '';
222 }) availablePackages
223 );
224 in
225 lib.optionals (availablePackages != [])
226 (deployAndroidPackages {
227 inherit os;
228 packages = availablePackages;
229 patchesInstructions = instructions;
230 })
231 ) systemImageTypes
232 ) platformVersions);
233
234 cmake = map (version:
235 callPackage ./cmake.nix {
236 inherit deployAndroidPackage os;
237 package = check-version packages "cmake" version;
238 }
239 ) cmakeVersions;
240
241 # Creates a NDK bundle.
242 makeNdkBundle = ndkVersion:
243 callPackage ./ndk-bundle {
244 inherit deployAndroidPackage os platform-tools;
245 package = packages.ndk-bundle.${ndkVersion} or packages.ndk.${ndkVersion};
246 };
247
248 # All NDK bundles.
249 ndk-bundles = lib.optionals includeNDK (map makeNdkBundle ndkVersions);
250
251 # The "default" NDK bundle.
252 ndk-bundle = if includeNDK then lib.findFirst (x: x != null) null ndk-bundles else null;
253
254 google-apis = map (version:
255 deployAndroidPackage {
256 inherit os;
257 package = (check-version addons "addons" version).google_apis;
258 }
259 ) (builtins.filter (platformVersion: platformVersion < "26") platformVersions); # API level 26 and higher include Google APIs by default
260
261 google-tv-addons = map (version:
262 deployAndroidPackage {
263 inherit os;
264 package = (check-version addons "addons" version).google_tv_addon;
265 }
266 ) platformVersions;
267
268 # Function that automatically links all plugins for which multiple versions can coexist
269 linkPlugins = {name, plugins}:
270 lib.optionalString (plugins != []) ''
271 mkdir -p ${name}
272 ${lib.concatMapStrings (plugin: ''
273 ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
274 '') plugins}
275 '';
276
277 # Function that automatically links all NDK plugins.
278 linkNdkPlugins = {name, plugins, rootName ? name}:
279 lib.optionalString (plugins != []) ''
280 mkdir -p ${rootName}
281 ${lib.concatMapStrings (plugin: ''
282 ln -s ${plugin}/libexec/android-sdk/${name} ${rootName}/${plugin.version}
283 '') plugins}
284 '';
285
286 # Function that automatically links the default NDK plugin.
287 linkNdkPlugin = {name, plugin, check}:
288 lib.optionalString check ''
289 ln -s ${plugin}/libexec/android-sdk/${name} ${name}
290 '';
291
292 # Function that automatically links a plugin for which only one version exists
293 linkPlugin = {name, plugin, check ? true}:
294 lib.optionalString check ''
295 ln -s ${plugin}/libexec/android-sdk/${name} ${name}
296 '';
297
298 linkSystemImages = { images, check }: lib.optionalString check ''
299 mkdir -p system-images
300 ${lib.concatMapStrings (system-image: ''
301 apiVersion=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*))
302 type=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*/*))
303 mkdir -p system-images/$apiVersion
304 ln -s ${system-image}/libexec/android-sdk/system-images/$apiVersion/$type system-images/$apiVersion/$type
305 '') images}
306 '';
307
308 # Links all plugins related to a requested platform
309 linkPlatformPlugins = {name, plugins, check}:
310 lib.optionalString check ''
311 mkdir -p ${name}
312 ${lib.concatMapStrings (plugin: ''
313 ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
314 '') plugins}
315 ''; # */
316
317 # This derivation deploys the tools package and symlinks all the desired
318 # plugins that we want to use. If the license isn't accepted, prints all the licenses
319 # requested and throws.
320 androidsdk = if !licenseAccepted then throw ''
321 ${builtins.concatStringsSep "\n\n" (mkLicenseTexts licenseNames)}
322
323 You must accept the following licenses:
324 ${lib.concatMapStringsSep "\n" (str: " - ${str}") licenseNames}
325
326 a)
327 by setting nixpkgs config option 'android_sdk.accept_license = true;'.
328 b)
329 by an environment variable for a single invocation of the nix tools.
330 $ export NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1
331 '' else callPackage ./cmdline-tools.nix {
332 inherit deployAndroidPackage os cmdLineToolsVersion;
333
334 package = check-version packages "cmdline-tools" cmdLineToolsVersion;
335
336 postInstall = ''
337 # Symlink all requested plugins
338 ${linkPlugin { name = "platform-tools"; plugin = platform-tools; }}
339 ${linkPlugin { name = "tools"; plugin = tools; check = toolsVersion != null; }}
340 ${linkPlugin { name = "patcher"; plugin = patcher; }}
341 ${linkPlugins { name = "build-tools"; plugins = build-tools; }}
342 ${linkPlugin { name = "emulator"; plugin = emulator; check = includeEmulator; }}
343 ${linkPlugins { name = "platforms"; plugins = platforms; }}
344 ${linkPlatformPlugins { name = "sources"; plugins = sources; check = includeSources; }}
345 ${linkPlugins { name = "cmake"; plugins = cmake; }}
346 ${linkNdkPlugins { name = "ndk-bundle"; rootName = "ndk"; plugins = ndk-bundles; }}
347 ${linkNdkPlugin { name = "ndk-bundle"; plugin = ndk-bundle; check = includeNDK; }}
348 ${linkSystemImages { images = system-images; check = includeSystemImages; }}
349 ${linkPlatformPlugins { name = "add-ons"; plugins = google-apis; check = useGoogleAPIs; }}
350 ${linkPlatformPlugins { name = "add-ons"; plugins = google-apis; check = useGoogleTVAddOns; }}
351
352 # Link extras
353 ${lib.concatMapStrings (identifier:
354 let
355 path = addons.extras.${identifier}.path;
356 addon = deployAndroidPackage {
357 inherit os;
358 package = addons.extras.${identifier};
359 };
360 in
361 ''
362 targetDir=$(dirname ${path})
363 mkdir -p $targetDir
364 ln -s ${addon}/libexec/android-sdk/${path} $targetDir
365 '') includeExtras}
366
367 # Expose common executables in bin/
368 mkdir -p $out/bin
369
370 for i in ${platform-tools}/bin/*; do
371 ln -s $i $out/bin
372 done
373
374 for i in ${emulator}/bin/*; do
375 ln -s $i $out/bin
376 done
377
378 find $ANDROID_SDK_ROOT/cmdline-tools/${cmdLineToolsVersion}/bin -type f -executable | while read i; do
379 ln -s $i $out/bin
380 done
381
382 # Write licenses
383 mkdir -p licenses
384 ${lib.concatMapStrings (licenseName:
385 let
386 licenseHashes = builtins.concatStringsSep "\n" (mkLicenseHashes licenseName);
387 licenseHashFile = writeText "androidenv-${licenseName}" licenseHashes;
388 in
389 ''
390 ln -s ${licenseHashFile} licenses/${licenseName}
391 '') licenseNames}
392 '';
393 };
394}