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