1{stdenv, lib, unzip, mkLicenses}:
2{packages, os ? null, nativeBuildInputs ? [], buildInputs ? [], patchesInstructions ? {}, meta ? {}, ...}@args:
3
4let
5 extraParams = removeAttrs args [ "packages" "os" "buildInputs" "nativeBuildInputs" "patchesInstructions" ];
6 sortedPackages = builtins.sort (x: y: builtins.lessThan x.name y.name) packages;
7
8 mkXmlAttrs = attrs:
9 lib.concatStrings (lib.mapAttrsToList (name: value: " ${name}=\"${value}\"") attrs);
10 mkXmlValues = attrs:
11 lib.concatStrings (lib.mapAttrsToList (name: value:
12 let
13 tag = builtins.head (builtins.match "([^:]+).*" name);
14 in
15 if builtins.typeOf value == "string" then "<${tag}>${value}</${tag}>" else mkXmlDoc name value
16 ) attrs);
17 mkXmlDoc = name: doc:
18 let
19 tag = builtins.head (builtins.match "([^:]+).*" name);
20 hasXmlAttrs = builtins.hasAttr "element-attributes" doc;
21 xmlValues = removeAttrs doc [ "element-attributes" ];
22 hasXmlValues = builtins.length (builtins.attrNames xmlValues) > 0;
23 in
24 if hasXmlAttrs && hasXmlValues then "<${tag}${mkXmlAttrs doc.element-attributes}>${mkXmlValues xmlValues }</${tag}>"
25 else if hasXmlAttrs && !hasXmlValues then "<${tag}${mkXmlAttrs doc.element-attributes}/>"
26 else if !hasXmlAttrs && hasXmlValues then "<${tag}>${mkXmlValues xmlValues}</${tag}>"
27 else "<${tag}/>";
28 mkXmlPackage = package: ''
29 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
30 <ns2:repository
31 xmlns:ns2="http://schemas.android.com/repository/android/common/02"
32 xmlns:ns3="http://schemas.android.com/repository/android/common/01"
33 xmlns:ns4="http://schemas.android.com/repository/android/generic/01"
34 xmlns:ns5="http://schemas.android.com/repository/android/generic/02"
35 xmlns:ns6="http://schemas.android.com/sdk/android/repo/addon2/01"
36 xmlns:ns7="http://schemas.android.com/sdk/android/repo/addon2/02"
37 xmlns:ns8="http://schemas.android.com/sdk/android/repo/addon2/03"
38 xmlns:ns9="http://schemas.android.com/sdk/android/repo/repository2/01"
39 xmlns:ns10="http://schemas.android.com/sdk/android/repo/repository2/02"
40 xmlns:ns11="http://schemas.android.com/sdk/android/repo/repository2/03"
41 xmlns:ns12="http://schemas.android.com/sdk/android/repo/sys-img2/03"
42 xmlns:ns13="http://schemas.android.com/sdk/android/repo/sys-img2/02"
43 xmlns:ns14="http://schemas.android.com/sdk/android/repo/sys-img2/01"
44 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
45 <license id="${package.license}" type="text">${lib.concatStringsSep "---" (mkLicenses package.license)}</license>
46 <localPackage path="${builtins.replaceStrings [ "/" ] [ ";" ] package.path}" obsolete="${
47 if (lib.hasAttrByPath [ "obsolete" ] package)
48 then package.obsolete else "false"
49 }">
50 ${mkXmlDoc "type-details" package.type-details}
51 ${mkXmlDoc "revision" package.revision-details}
52 ${lib.optionalString (lib.hasAttrByPath [ "dependencies" ] package)
53 (mkXmlDoc "dependencies" package.dependencies)
54 }
55 <display-name>${package.displayName}</display-name>
56 <uses-license ref="${package.license}"/>
57 </localPackage>
58 </ns2:repository>
59 '';
60in
61stdenv.mkDerivation ({
62 inherit buildInputs;
63 pname = "android-sdk-${lib.concatMapStringsSep "-" (package: package.name) sortedPackages}";
64 version = lib.concatMapStringsSep "-" (package: package.revision) sortedPackages;
65 src = map (package:
66 if os != null && builtins.hasAttr os package.archives then package.archives.${os} else package.archives.all
67 ) packages;
68 nativeBuildInputs = [ unzip ] ++ nativeBuildInputs;
69 preferLocalBuild = true;
70
71 unpackPhase = ''
72 buildDir=$PWD
73 i=0
74 for srcArchive in $src; do
75 extractedZip="extractedzip-$i"
76 i=$((i+1))
77 cd "$buildDir"
78 mkdir "$extractedZip"
79 cd "$extractedZip"
80 unpackFile "$srcArchive"
81 done
82 '';
83
84 installPhase = lib.concatStrings (lib.imap0 (i: package: ''
85 cd $buildDir/extractedzip-${toString i}
86
87 # Most Android Zip packages have a root folder, but some don't. We unpack
88 # the zip file in a folder and we try to discover whether it has a single root
89 # folder. If this is the case, we adjust the current working folder.
90 if [ "$(find . -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ]; then
91 cd "$(find . -mindepth 1 -maxdepth 1 -type d)"
92 fi
93 extractedZip="$PWD"
94
95 packageBaseDir=$out/libexec/android-sdk/${package.path}
96 mkdir -p $packageBaseDir
97 cd $packageBaseDir
98 cp -a $extractedZip/* .
99 ${patchesInstructions.${package.name}}
100
101 if [ ! -f $packageBaseDir/package.xml ]; then
102 cat << EOF > $packageBaseDir/package.xml
103 ${mkXmlPackage package}
104 EOF
105 fi
106 '') packages);
107
108 # Some executables that have been patched with patchelf may not work any longer after they have been stripped.
109 dontStrip = true;
110 dontPatchELF = true;
111 dontAutoPatchelf = true;
112
113 meta = {
114 description = lib.concatMapStringsSep "\n" (package: package.displayName) packages;
115 } // meta;
116} // extraParams)