nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at python-updates 805 lines 24 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 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}