nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1{
2 callPackage,
3 stdenv,
4 stdenvNoCC,
5 lib,
6 fetchurl,
7 ruby,
8 writeText,
9 licenseAccepted ? false,
10 meta,
11}:
12
13let
14 # Coerces a string to an int.
15 coerceInt = val: if lib.isInt val then val else lib.toIntBase10 val;
16
17 coerceIntVersion = v: coerceInt (lib.versions.major (toString v));
18
19 # Parses a single version, substituting "latest" with the latest version.
20 parseVersion =
21 repo: key: version:
22 if version == "latest" then repo.latest.${key} else version;
23
24 # Parses a list of versions, substituting "latest" with the latest version.
25 parseVersions =
26 repo: key: versions:
27 lib.sort (a: b: lib.strings.compareVersions (toString a) (toString b) > 0) (
28 lib.unique (map (parseVersion repo key) versions)
29 );
30in
31{
32 repoJson ? ./repo.json,
33 repoXmls ? null,
34 repo ? (
35 # Reads the repo JSON. If repoXmls is provided, will build a repo JSON into the Nix store.
36 if repoXmls != null then
37 let
38 # Uses update.rb to create a repo spec.
39 mkRepoJson =
40 {
41 packages ? [ ],
42 images ? [ ],
43 addons ? [ ],
44 }:
45 let
46 mkRepoRuby = (
47 ruby.withPackages (
48 pkgs: with pkgs; [
49 slop
50 curb
51 nokogiri
52 ]
53 )
54 );
55 mkRepoRubyArguments = lib.lists.flatten [
56 (map (package: [
57 "--packages"
58 "${package}"
59 ]) packages)
60 (map (image: [
61 "--images"
62 "${image}"
63 ]) images)
64 (map (addon: [
65 "--addons"
66 "${addon}"
67 ]) addons)
68 ];
69 in
70 stdenvNoCC.mkDerivation {
71 name = "androidenv-repo-json";
72 buildInputs = [ mkRepoRuby ];
73 preferLocalBuild = true;
74 unpackPhase = "true";
75 buildPhase = ''
76 env ruby -e 'load "${./update.rb}"' -- ${lib.escapeShellArgs mkRepoRubyArguments} --input /dev/null --output repo.json
77 '';
78 installPhase = ''
79 mv repo.json $out
80 '';
81 };
82 repoXmlSpec = {
83 packages = repoXmls.packages or [ ];
84 images = repoXmls.images or [ ];
85 addons = repoXmls.addons or [ ];
86 };
87 in
88 lib.importJSON "${mkRepoJson repoXmlSpec}"
89 else
90 lib.importJSON repoJson
91 ),
92 cmdLineToolsVersion ? "latest",
93 toolsVersion ? "latest",
94 platformToolsVersion ? "latest",
95 buildToolsVersions ? [ "latest" ],
96 includeEmulator ? false,
97 emulatorVersion ? "latest",
98 minPlatformVersion ? null,
99 maxPlatformVersion ? "latest",
100 numLatestPlatformVersions ? 1,
101 platformVersions ?
102 if minPlatformVersion != null && maxPlatformVersion != null then
103 # Range between min and max, inclusive.
104 let
105 minPlatformVersion' = parseVersion repo "platforms" minPlatformVersion;
106 maxPlatformVersion' = parseVersion repo "platforms" maxPlatformVersion;
107 minPlatformVersionInt = coerceIntVersion minPlatformVersion';
108 maxPlatformVersionInt = coerceIntVersion maxPlatformVersion';
109 range = lib.range (lib.min minPlatformVersionInt maxPlatformVersionInt) (
110 lib.max minPlatformVersionInt maxPlatformVersionInt
111 );
112 in
113 # Don't use the actual latest version in lieu of the rounded version here,
114 # since when Google upgrades it would have the nasty side effect of being
115 # unstable and picking 35 -> 36 -> 37 instead of 35 -> 36.1 -> 37.
116 # Best to stay consistent.
117 #
118 # However, if only one platform is requested and it's the latest (which is the default),
119 # we should use it.
120 if lib.length range == 1 then lib.singleton maxPlatformVersion' else range
121 else
122 # Use numLatestPlatformVersions with a lower cutoff of minPlatformVersion (defaulting to 1)
123 # to determine how many of the latest *major* versions we should pick.
124 let
125 minPlatformVersionInt =
126 if minPlatformVersion == null then
127 1
128 else
129 coerceIntVersion (parseVersion repo "platforms" minPlatformVersion);
130 latestPlatformVersionInt = lib.max minPlatformVersionInt (coerceIntVersion repo.latest.platforms);
131 firstPlatformVersionInt = lib.max minPlatformVersionInt (
132 latestPlatformVersionInt - (lib.max 1 numLatestPlatformVersions) + 1
133 );
134 range = lib.range firstPlatformVersionInt latestPlatformVersionInt;
135 in
136 # Ditto, see above.
137 if lib.length range == 1 then lib.singleton repo.latest.platforms else range,
138 includeSources ? false,
139 includeSystemImages ? false,
140 systemImageTypes ? [
141 "google_apis"
142 "google_apis_playstore"
143 ],
144 abiVersions ? [
145 "x86"
146 "x86_64"
147 "armeabi-v7a"
148 "arm64-v8a"
149 ],
150 # cmake has precompiles on x86_64 and Darwin platforms. Default to true there for compatibility.
151 includeCmake ? stdenv.hostPlatform.isx86_64 || stdenv.hostPlatform.isDarwin,
152 cmakeVersions ? [ "latest" ],
153 includeNDK ? false,
154 ndkVersion ? "latest",
155 ndkVersions ? [ ndkVersion ],
156 useGoogleAPIs ? false,
157 useGoogleTVAddOns ? false,
158 includeExtras ? [ ],
159 extraLicenses ? [ ],
160}:
161
162let
163 # Resolve all the platform versions.
164 platformVersions' = parseVersions repo "platforms" platformVersions;
165
166 # Determine the Android os identifier from Nix's system identifier
167 os =
168 {
169 x86_64-linux = "linux";
170 x86_64-darwin = "macosx";
171 aarch64-linux = "linux";
172 aarch64-darwin = "macosx";
173 }
174 .${stdenv.hostPlatform.system} or "all";
175
176 # Determine the Android arch identifier from Nix's system identifier
177 arch =
178 {
179 x86_64-linux = "x64";
180 x86_64-darwin = "x64";
181 aarch64-linux = "aarch64";
182 aarch64-darwin = "aarch64";
183 }
184 .${stdenv.hostPlatform.system} or "all";
185
186 # Converts all 'archives' keys in a repo spec to fetchurl calls.
187 fetchArchives =
188 attrSet:
189 lib.attrsets.mapAttrsRecursive (
190 path: value:
191 if (builtins.elemAt path (builtins.length path - 1)) == "archives" then
192 let
193 validArchives = builtins.filter (
194 archive:
195 let
196 isTargetOs =
197 if builtins.hasAttr "os" archive then archive.os == os || archive.os == "all" else true;
198 isTargetArch =
199 if builtins.hasAttr "arch" archive then archive.arch == arch || archive.arch == "all" else true;
200 in
201 isTargetOs && isTargetArch
202 ) value;
203 packageInfo = lib.attrByPath (lib.sublist 0 (builtins.length path - 1) path) null attrSet;
204 in
205 lib.optionals (builtins.length validArchives > 0) (
206 lib.last (
207 map (
208 archive:
209 (fetchurl {
210 pname = packageInfo.name;
211 version = packageInfo.revision;
212 inherit (archive) url sha1;
213 inherit meta;
214 passthru = {
215 info = packageInfo;
216 };
217 }).overrideAttrs
218 (
219 finalAttrs: previousAttrs: {
220 # fetchurl prioritize `pname` and `version` over the specified `name`,
221 # so specify custom `name` in an override.
222 name = baseNameOf (lib.head (finalAttrs.urls));
223 }
224 )
225 ) validArchives
226 )
227 )
228 else
229 value
230 ) attrSet;
231
232 # Converts the repo attrset into fetch calls.
233 allArchives = {
234 packages = fetchArchives repo.packages;
235 system-images = fetchArchives repo.images;
236 addons = fetchArchives repo.addons;
237 extras = fetchArchives repo.extras;
238 };
239
240 # Lift the archives to the package level for easy search,
241 # and add recurseIntoAttrs to all of them.
242 allPackages =
243 let
244 liftedArchives = lib.attrsets.mapAttrsRecursiveCond (value: !(builtins.hasAttr "archives" value)) (
245 path: value:
246 if (value.archives or null) != null && (value.archives or [ ]) != [ ] then
247 lib.dontRecurseIntoAttrs value.archives
248 else
249 null
250 ) allArchives;
251
252 # Creates a version key from a name.
253 # Converts things like 'extras;google;auto' to 'extras-google-auto'
254 toVersionKey =
255 name:
256 let
257 normalizedName = lib.replaceStrings [ ";" "." ] [ "-" "_" ] name;
258 versionParts = lib.match "^([0-9][0-9\\.]*)(.*)$" normalizedName;
259 in
260 if versionParts == null then normalizedName else "v" + lib.concatStrings versionParts;
261
262 recurse = lib.mapAttrs' (
263 name: value:
264 if builtins.isAttrs value && (value.recurseForDerivations or true) then
265 lib.nameValuePair (toVersionKey name) (lib.recurseIntoAttrs (recurse value))
266 else
267 lib.nameValuePair (toVersionKey name) value
268 );
269 in
270 lib.recurseIntoAttrs (recurse liftedArchives);
271
272 # Converts a license name to a list of license texts.
273 mkLicenses = licenseName: repo.licenses.${licenseName};
274
275 # Converts a list of license names to a flattened list of license texts.
276 # Just used for displaying licenses.
277 mkLicenseTexts =
278 licenseNames:
279 lib.lists.flatten (
280 map (
281 licenseName: map (licenseText: "--- ${licenseName} ---\n${licenseText}") (mkLicenses licenseName)
282 ) licenseNames
283 );
284
285 # Converts a license name to a list of license hashes.
286 mkLicenseHashes =
287 licenseName: map (licenseText: builtins.hashString "sha1" licenseText) (mkLicenses licenseName);
288
289 # The list of all license names we're accepting. Put android-sdk-license there
290 # by default.
291 licenseNames = lib.lists.unique (
292 [
293 "android-sdk-license"
294 ]
295 ++ extraLicenses
296 );
297
298 # Returns true if the given version exists.
299 hasVersion =
300 packages: package: version:
301 lib.hasAttrByPath [ package (toString version) ] packages;
302
303 # Displays a nice error message that includes the available options if a version doesn't exist.
304 # Note that allPackages can be a list of package sets, or a single package set. Pass a list if
305 # you want to prioritize elements to the left (e.g. for passing a platform major version).
306 checkVersion =
307 allPackages: package: version:
308 let
309 # Convert the package sets to a list.
310 allPackages' = if lib.isList allPackages then allPackages else lib.singleton allPackages;
311
312 # Pick the first package set where we have the version.
313 packageSet = lib.findFirst (packages: hasVersion packages package version) null allPackages';
314 in
315 if packageSet == null then
316 throw ''
317 The version ${toString version} is missing in package ${package}.
318 The only available versions are ${
319 lib.concatStringsSep ", " (
320 lib.attrNames (lib.foldl (s: x: s // (x.${package} or { })) { } allPackages')
321 )
322 }.
323 ''
324 else
325 packageSet.${package}.${toString version};
326
327 # Returns true if we should link the specified plugins.
328 shouldLink =
329 check: packages:
330 assert builtins.isList packages;
331 if check == true then
332 true
333 else if check == false then
334 false
335 else if check == "if-supported" then
336 let
337 hasSrc =
338 package: package.src != null && (builtins.isList package.src -> builtins.length package.src > 0);
339 in
340 packages != [ ] && lib.all hasSrc packages
341 else
342 throw "Invalid argument ${toString check}; use true, false, or if-supported";
343
344 # Function that automatically links all plugins for which multiple versions can coexist
345 linkPlugins =
346 {
347 name,
348 plugins,
349 check ? true,
350 }:
351 lib.optionalString (shouldLink check plugins) ''
352 mkdir -p ${name}
353 ${lib.concatMapStrings (plugin: ''
354 ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
355 '') plugins}
356 '';
357
358 # Function that automatically links all NDK plugins.
359 linkNdkPlugins =
360 {
361 name,
362 plugins,
363 rootName ? name,
364 check ? true,
365 }:
366 lib.optionalString (shouldLink check plugins) ''
367 mkdir -p ${rootName}
368 ${lib.concatMapStrings (plugin: ''
369 ln -s ${plugin}/libexec/android-sdk/${name} ${rootName}/${plugin.version}
370 '') plugins}
371 '';
372
373 # Function that automatically links the default NDK plugin.
374 linkNdkPlugin =
375 {
376 name,
377 plugin,
378 check,
379 }:
380 lib.optionalString (shouldLink check [ plugin ]) ''
381 ln -s ${plugin}/libexec/android-sdk/${name} ${name}
382 '';
383
384 # Function that automatically links a plugin for which only one version exists
385 linkPlugin =
386 {
387 name,
388 plugin,
389 check ? true,
390 }:
391 lib.optionalString (shouldLink check [ plugin ]) ''
392 ln -s ${plugin}/libexec/android-sdk/${name} ${name}
393 '';
394
395 linkSystemImages =
396 { images, check }:
397 lib.optionalString (shouldLink check images) ''
398 mkdir -p system-images
399 ${lib.concatMapStrings (system-image: ''
400 apiVersion=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*))
401 type=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*/*))
402 mkdir -p system-images/$apiVersion
403 ln -s ${system-image}/libexec/android-sdk/system-images/$apiVersion/$type system-images/$apiVersion/$type
404 '') images}
405 '';
406
407 # Links all plugins related to a requested platform
408 linkPlatformPlugins =
409 {
410 name,
411 plugins,
412 check,
413 }:
414 lib.optionalString (shouldLink check plugins) ''
415 mkdir -p ${name}
416 ${lib.concatMapStrings (plugin: ''
417 ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
418 '') plugins}
419 ''; # */
420
421in
422lib.recurseIntoAttrs rec {
423 deployAndroidPackages = callPackage ./deploy-androidpackages.nix {
424 inherit
425 stdenv
426 lib
427 mkLicenses
428 meta
429 os
430 arch
431 ;
432 };
433
434 deployAndroidPackage = (
435 {
436 package,
437 buildInputs ? [ ],
438 patchInstructions ? "",
439 meta ? { },
440 ...
441 }@args:
442 let
443 extraParams = removeAttrs args [
444 "package"
445 "os"
446 "arch"
447 "buildInputs"
448 "patchInstructions"
449 ];
450 in
451 deployAndroidPackages (
452 {
453 inherit buildInputs;
454 packages = [ package ];
455 patchesInstructions = {
456 "${package.name}" = patchInstructions;
457 };
458 }
459 // extraParams
460 )
461 );
462
463 all = allPackages;
464
465 platform-tools = callPackage ./platform-tools.nix {
466 inherit
467 deployAndroidPackage
468 os
469 arch
470 meta
471 ;
472 package = checkVersion allArchives.packages "platform-tools" (
473 parseVersion repo "platform-tools" platformToolsVersion
474 );
475 };
476
477 tools = callPackage ./tools.nix {
478 inherit
479 deployAndroidPackage
480 os
481 arch
482 meta
483 ;
484 package = checkVersion allArchives.packages "tools" (parseVersion repo "tools" toolsVersion);
485
486 postInstall = ''
487 ${linkPlugin {
488 name = "platform-tools";
489 plugin = platform-tools;
490 }}
491 ${linkPlugin {
492 name = "emulator";
493 plugin = emulator;
494 check = includeEmulator;
495 }}
496 '';
497 };
498
499 build-tools = map (
500 version:
501 callPackage ./build-tools.nix {
502 inherit
503 deployAndroidPackage
504 os
505 arch
506 meta
507 ;
508 package = checkVersion allArchives.packages "build-tools" version;
509
510 postInstall = ''
511 ${linkPlugin {
512 name = "tools";
513 plugin = tools;
514 check = toolsVersion != null;
515 }}
516 '';
517 }
518 ) (parseVersions repo "build-tools" buildToolsVersions);
519
520 emulator = callPackage ./emulator.nix {
521 inherit
522 deployAndroidPackage
523 os
524 arch
525 meta
526 ;
527 package = checkVersion allArchives.packages "emulator" (
528 parseVersion repo "emulator" emulatorVersion
529 );
530
531 postInstall = ''
532 ${linkSystemImages {
533 images = system-images;
534 check = includeSystemImages;
535 }}
536 '';
537 };
538
539 # This is a list of the chosen API levels, as integers.
540 platformVersions = platformVersions';
541
542 platforms = map (
543 version:
544 deployAndroidPackage {
545 package = checkVersion allArchives.packages "platforms" version;
546 }
547 ) platformVersions';
548
549 sources = map (
550 version:
551 deployAndroidPackage {
552 package = checkVersion allArchives.packages "sources" version;
553 }
554 ) platformVersions';
555
556 system-images = lib.flatten (
557 map (
558 apiVersion:
559 map (
560 type:
561 # Deploy all system images with the same systemImageType in one derivation to avoid the `null` problem below
562 # with avdmanager when trying to create an avd!
563 #
564 # ```
565 # $ yes "" | avdmanager create avd --force --name testAVD --package 'system-images;android-33;google_apis;x86_64'
566 # Error: Package path is not valid. Valid system image paths are:
567 # null
568 # ```
569 let
570 availablePackages =
571 map (abiVersion: allArchives.system-images.${toString apiVersion}.${type}.${abiVersion})
572 (
573 builtins.filter (
574 abiVersion: lib.hasAttrByPath [ (toString apiVersion) type abiVersion ] allArchives.system-images
575 ) abiVersions
576 );
577
578 instructions = lib.listToAttrs (
579 map (package: {
580 name = package.name;
581 value = lib.optionalString (lib.hasPrefix "google_apis" type) ''
582 # Patch 'google_apis' system images so they're recognized by the sdk.
583 # Without this, `android list targets` shows 'Tag/ABIs : no ABIs' instead
584 # of 'Tag/ABIs : google_apis*/*' and the emulator fails with an ABI-related error.
585 sed -i '/^Addon.Vendor/d' source.properties
586 '';
587 }) availablePackages
588 );
589 in
590 lib.optionals (availablePackages != [ ]) (deployAndroidPackages {
591 packages = availablePackages;
592 patchesInstructions = instructions;
593 })
594 ) systemImageTypes
595 ) platformVersions'
596 );
597
598 cmake = map (
599 version:
600 callPackage ./cmake.nix {
601 inherit
602 deployAndroidPackage
603 os
604 arch
605 meta
606 ;
607 package = checkVersion allArchives.packages "cmake" version;
608 }
609 ) (parseVersions repo "cmake" cmakeVersions);
610
611 # All NDK bundles.
612 ndk-bundles =
613 let
614 # Creates a NDK bundle.
615 makeNdkBundle =
616 package:
617 callPackage ./ndk-bundle {
618 inherit
619 deployAndroidPackage
620 os
621 arch
622 platform-tools
623 meta
624 package
625 ;
626 };
627 in
628 lib.flatten (
629 map (
630 version:
631 let
632 package = makeNdkBundle (
633 allArchives.packages.ndk-bundle.${version} or allArchives.packages.ndk.${version}
634 );
635 in
636 lib.optional (shouldLink includeNDK [ package ]) package
637 ) (parseVersions repo "ndk" ndkVersions)
638 );
639
640 # The "default" NDK bundle.
641 ndk-bundle = if ndk-bundles == [ ] then null else lib.head ndk-bundles;
642
643 # Makes a Google API bundle from supported versions.
644 google-apis = map (
645 version:
646 deployAndroidPackage {
647 package = (checkVersion allArchives "addons" version).google_apis;
648 }
649 ) (lib.filter (hasVersion allArchives "addons") platformVersions');
650
651 # Makes a Google TV addons bundle from supported versions.
652 google-tv-addons = map (
653 version:
654 deployAndroidPackage {
655 package = (checkVersion allArchives "addons" version).google_tv_addon;
656 }
657 ) (lib.filter (hasVersion allArchives "addons") platformVersions');
658
659 cmdline-tools-package = checkVersion allArchives.packages "cmdline-tools" (
660 parseVersion repo "cmdline-tools" cmdLineToolsVersion
661 );
662
663 # This derivation deploys the tools package and symlinks all the desired
664 # plugins that we want to use. If the license isn't accepted, prints all the licenses
665 # requested and throws.
666 androidsdk = callPackage ./cmdline-tools.nix {
667 inherit
668 deployAndroidPackage
669 os
670 arch
671 meta
672 ;
673
674 package = cmdline-tools-package;
675
676 postInstall =
677 if !licenseAccepted then
678 throw ''
679 ${builtins.concatStringsSep "\n\n" (mkLicenseTexts licenseNames)}
680
681 You must accept the following licenses:
682 ${lib.concatMapStringsSep "\n" (str: " - ${str}") licenseNames}
683
684 a)
685 by setting nixpkgs config option 'android_sdk.accept_license = true;'.
686 b)
687 by an environment variable for a single invocation of the nix tools.
688 $ export NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1
689 ''
690 else
691 ''
692 # Symlink all requested plugins
693 ${linkPlugin {
694 name = "platform-tools";
695 plugin = platform-tools;
696 }}
697 ${linkPlugin {
698 name = "tools";
699 plugin = tools;
700 check = toolsVersion != null;
701 }}
702 ${linkPlugins {
703 name = "build-tools";
704 plugins = build-tools;
705 }}
706 ${linkPlugin {
707 name = "emulator";
708 plugin = emulator;
709 check = includeEmulator;
710 }}
711 ${linkPlugins {
712 name = "platforms";
713 plugins = platforms;
714 }}
715 ${linkPlatformPlugins {
716 name = "sources";
717 plugins = sources;
718 check = includeSources;
719 }}
720 ${linkPlugins {
721 name = "cmake";
722 plugins = cmake;
723 check = includeCmake;
724 }}
725 ${linkNdkPlugins {
726 name = "ndk-bundle";
727 rootName = "ndk";
728 plugins = ndk-bundles;
729 check = includeNDK;
730 }}
731 ${linkNdkPlugin {
732 name = "ndk-bundle";
733 plugin = ndk-bundle;
734 check = includeNDK;
735 }}
736 ${linkSystemImages {
737 images = system-images;
738 check = includeSystemImages;
739 }}
740 ${linkPlatformPlugins {
741 name = "add-ons";
742 plugins = google-apis;
743 check = useGoogleAPIs;
744 }}
745 ${linkPlatformPlugins {
746 name = "add-ons";
747 plugins = google-tv-addons;
748 check = useGoogleTVAddOns;
749 }}
750
751 # Link extras
752 ${lib.concatMapStrings (
753 identifier:
754 let
755 package = allArchives.extras.${identifier};
756 path = package.path;
757 extras = callPackage ./extras.nix {
758 inherit
759 deployAndroidPackage
760 package
761 os
762 arch
763 meta
764 ;
765 };
766 in
767 ''
768 targetDir=$(dirname ${path})
769 mkdir -p $targetDir
770 ln -s ${extras}/libexec/android-sdk/${path} $targetDir
771 ''
772 ) includeExtras}
773
774 # Expose common executables in bin/
775 mkdir -p $out/bin
776
777 for i in ${platform-tools}/bin/*; do
778 ln -s $i $out/bin
779 done
780
781 ${lib.optionalString (shouldLink includeEmulator [ emulator ]) ''
782 for i in ${emulator}/bin/*; do
783 ln -s $i $out/bin
784 done
785 ''}
786
787 find $ANDROID_SDK_ROOT/${cmdline-tools-package.path}/bin -type f -executable | while read i; do
788 ln -s $i $out/bin
789 done
790
791 # Write licenses
792 mkdir -p licenses
793 ${lib.concatMapStrings (
794 licenseName:
795 let
796 licenseHashes = builtins.concatStringsSep "\n" (mkLicenseHashes licenseName);
797 licenseHashFile = writeText "androidenv-${licenseName}" licenseHashes;
798 in
799 ''
800 ln -s ${licenseHashFile} licenses/${licenseName}
801 ''
802 ) licenseNames}
803 '';
804 };
805}