push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
/xml
local.properties
.android

View File

@@ -0,0 +1,13 @@
# How to update
`nix-shell maintainers/scripts/update.nix --argstr package androidenv.test-suite --argstr commit true`
# How to run tests
You may need to make yourself familiar with [package tests](../../../README.md#package-tests) and [Writing larger package tests](../../../README.md#writing-larger-package-tests), then run tests locally with:
```shell
$ export NIXPKGS_ALLOW_UNFREE=1
$ cd path/to/nixpkgs
$ nix-build -A androidenv.test-suite
```

View File

@@ -0,0 +1,72 @@
{
composeAndroidPackages,
stdenv,
lib,
ant,
jdk,
gnumake,
gawk,
meta,
}:
{
name,
release ? false,
keyStore ? null,
keyAlias ? null,
keyStorePassword ? null,
keyAliasPassword ? null,
antFlags ? "",
...
}@args:
assert
release
-> keyStore != null && keyAlias != null && keyStorePassword != null && keyAliasPassword != null;
let
androidSdkFormalArgs = lib.functionArgs composeAndroidPackages;
androidArgs = builtins.intersectAttrs androidSdkFormalArgs args;
androidsdk = (composeAndroidPackages androidArgs).androidsdk;
extraArgs = removeAttrs args ([ "name" ] ++ builtins.attrNames androidSdkFormalArgs);
in
stdenv.mkDerivation (
{
name = lib.replaceStrings [ " " ] [ "" ] name; # Android APKs may contain white spaces in their names, but Nix store paths cannot
ANDROID_HOME = "${androidsdk}/libexec/android-sdk";
buildInputs = [
jdk
ant
];
buildPhase = ''
${lib.optionalString release ''
# Provide key singing attributes
( echo "key.store=${keyStore}"
echo "key.alias=${keyAlias}"
echo "key.store.password=${keyStorePassword}"
echo "key.alias.password=${keyAliasPassword}"
) >> ant.properties
''}
export ANDROID_SDK_HOME=`pwd` # Key files cannot be stored in the user's home directory. This overrides it.
${lib.optionalString (args ? includeNDK && args.includeNDK) ''
export GNUMAKE=${gnumake}/bin/make
export NDK_HOST_AWK=${gawk}/bin/gawk
${androidsdk}/libexec/android-sdk/ndk-bundle/ndk-build
''}
ant ${antFlags} ${if release then "release" else "debug"}
'';
installPhase = ''
mkdir -p $out
mv bin/*-${if release then "release" else "debug"}.apk $out
mkdir -p $out/nix-support
echo "file binary-dist \"$(echo $out/*.apk)\"" > $out/nix-support/hydra-build-products
'';
inherit meta;
}
// extraArgs
)

View File

@@ -0,0 +1,55 @@
{
deployAndroidPackage,
lib,
stdenv,
package,
os,
arch,
autoPatchelfHook,
makeWrapper,
pkgs,
pkgsi686Linux,
postInstall,
meta,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = [ makeWrapper ] ++ lib.optionals (os == "linux") [ autoPatchelfHook ];
buildInputs =
lib.optionals (os == "linux") [
pkgs.glibc
pkgs.zlib
pkgs.ncurses5
pkgs.libcxx
]
++ lib.optionals (os == "linux" && stdenv.hostPlatform.isx86_64) (
with pkgsi686Linux;
[
glibc
zlib
ncurses5
]
);
patchInstructions = ''
${lib.optionalString (os == "linux") ''
addAutoPatchelfSearchPath $packageBaseDir/lib
if [[ -d $packageBaseDir/lib64 ]]; then
addAutoPatchelfSearchPath $packageBaseDir/lib64
autoPatchelf --no-recurse $packageBaseDir/lib64
fi
autoPatchelf --no-recurse $packageBaseDir
''}
${lib.optionalString (lib.toInt (lib.versions.major package.revision) < 33) ''
wrapProgram $PWD/mainDexClasses \
--prefix PATH : ${pkgs.jdk8}/bin
''}
cd $out/libexec/android-sdk
''
+ postInstall;
noAuditTmpdir = true; # The checker script gets confused by the build-tools path that is incorrectly identified as a reference to /build
inherit meta;
}

View File

@@ -0,0 +1,28 @@
{
deployAndroidPackage,
lib,
package,
os,
arch,
autoPatchelfHook,
pkgs,
stdenv,
meta,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ];
buildInputs = lib.optionals (os == "linux") [
pkgs.stdenv.cc.libc
pkgs.stdenv.cc.cc
pkgs.ncurses5
];
patchInstructions = lib.optionalString (os == "linux") ''
autoPatchelf $packageBaseDir/bin
'';
meta = meta // {
license = lib.licenses.bsd3;
};
}

View File

@@ -0,0 +1,53 @@
{
deployAndroidPackage,
lib,
package,
autoPatchelfHook,
makeWrapper,
os,
arch,
pkgs,
stdenv,
postInstall,
meta,
}:
deployAndroidPackage {
name = "androidsdk";
inherit package os arch;
nativeBuildInputs = [
makeWrapper
]
++ lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ];
patchInstructions = ''
${lib.optionalString (os == "linux") ''
# Auto patch all binaries
autoPatchelf .
''}
# Strip double dots from the root path
export ANDROID_SDK_ROOT="$out/libexec/android-sdk"
# Wrap all scripts that require JAVA_HOME
find $ANDROID_SDK_ROOT/${package.path}/bin -maxdepth 1 -type f -executable | while read program; do
if grep -q "JAVA_HOME" $program; then
wrapProgram $program --prefix PATH : ${pkgs.jdk17}/bin \
--prefix ANDROID_SDK_ROOT : $ANDROID_SDK_ROOT
fi
done
# Wrap sdkmanager script
wrapProgram $ANDROID_SDK_ROOT/${package.path}/bin/sdkmanager \
--prefix PATH : ${lib.makeBinPath [ pkgs.jdk17 ]} \
--add-flags "--sdk_root=$ANDROID_SDK_ROOT"
# Patch all script shebangs
patchShebangs $ANDROID_SDK_ROOT/${package.path}/bin
cd $ANDROID_SDK_ROOT
${postInstall}
'';
inherit meta;
}

View File

@@ -0,0 +1,771 @@
{
callPackage,
stdenv,
stdenvNoCC,
lib,
fetchurl,
ruby,
writeText,
licenseAccepted ? false,
meta,
}:
let
# Coerces a string to an int.
coerceInt = val: if lib.isInt val then val else lib.toIntBase10 val;
# Parses a single version, substituting "latest" with the latest version.
parseVersion =
repo: key: version:
if version == "latest" then repo.latest.${key} else version;
# Parses a list of versions, substituting "latest" with the latest version.
parseVersions =
repo: key: versions:
lib.unique (map (parseVersion repo key) versions);
in
{
repoJson ? ./repo.json,
repoXmls ? null,
repo ? (
# Reads the repo JSON. If repoXmls is provided, will build a repo JSON into the Nix store.
if repoXmls != null then
let
# Uses update.rb to create a repo spec.
mkRepoJson =
{
packages ? [ ],
images ? [ ],
addons ? [ ],
}:
let
mkRepoRuby = (
ruby.withPackages (
pkgs: with pkgs; [
slop
curb
nokogiri
]
)
);
mkRepoRubyArguments = lib.lists.flatten [
(map (package: [
"--packages"
"${package}"
]) packages)
(map (image: [
"--images"
"${image}"
]) images)
(map (addon: [
"--addons"
"${addon}"
]) addons)
];
in
stdenvNoCC.mkDerivation {
name = "androidenv-repo-json";
buildInputs = [ mkRepoRuby ];
preferLocalBuild = true;
unpackPhase = "true";
buildPhase = ''
env ruby -e 'load "${./update.rb}"' -- ${lib.escapeShellArgs mkRepoRubyArguments} --input /dev/null --output repo.json
'';
installPhase = ''
mv repo.json $out
'';
};
repoXmlSpec = {
packages = repoXmls.packages or [ ];
images = repoXmls.images or [ ];
addons = repoXmls.addons or [ ];
};
in
lib.importJSON "${mkRepoJson repoXmlSpec}"
else
lib.importJSON repoJson
),
cmdLineToolsVersion ? "latest",
toolsVersion ? "latest",
platformToolsVersion ? "latest",
buildToolsVersions ? [ "latest" ],
includeEmulator ? false,
emulatorVersion ? "latest",
minPlatformVersion ? null,
maxPlatformVersion ? "latest",
numLatestPlatformVersions ? 1,
platformVersions ?
if minPlatformVersion != null && maxPlatformVersion != null then
let
minPlatformVersionInt = coerceInt (parseVersion repo "platforms" minPlatformVersion);
maxPlatformVersionInt = coerceInt (parseVersion repo "platforms" maxPlatformVersion);
in
lib.range (lib.min minPlatformVersionInt maxPlatformVersionInt) (
lib.max minPlatformVersionInt maxPlatformVersionInt
)
else
let
minPlatformVersionInt =
if minPlatformVersion == null then
1
else
coerceInt (parseVersion repo "platforms" minPlatformVersion);
latestPlatformVersionInt = lib.max minPlatformVersionInt (coerceInt repo.latest.platforms);
firstPlatformVersionInt = lib.max minPlatformVersionInt (
latestPlatformVersionInt - (lib.max 1 numLatestPlatformVersions) + 1
);
in
lib.range firstPlatformVersionInt latestPlatformVersionInt,
includeSources ? false,
includeSystemImages ? false,
systemImageTypes ? [
"google_apis"
"google_apis_playstore"
],
abiVersions ? [
"x86"
"x86_64"
"armeabi-v7a"
"arm64-v8a"
],
# cmake has precompiles on x86_64 and Darwin platforms. Default to true there for compatibility.
includeCmake ? stdenv.hostPlatform.isx86_64 || stdenv.hostPlatform.isDarwin,
cmakeVersions ? [ "latest" ],
includeNDK ? false,
ndkVersion ? "latest",
ndkVersions ? [ ndkVersion ],
useGoogleAPIs ? false,
useGoogleTVAddOns ? false,
includeExtras ? [ ],
extraLicenses ? [ ],
}:
let
# Resolve all the platform versions.
platformVersions' = map coerceInt (parseVersions repo "platforms" platformVersions);
# Determine the Android os identifier from Nix's system identifier
os =
{
x86_64-linux = "linux";
x86_64-darwin = "macosx";
aarch64-linux = "linux";
aarch64-darwin = "macosx";
}
.${stdenv.hostPlatform.system} or "all";
# Determine the Android arch identifier from Nix's system identifier
arch =
{
x86_64-linux = "x64";
x86_64-darwin = "x64";
aarch64-linux = "aarch64";
aarch64-darwin = "aarch64";
}
.${stdenv.hostPlatform.system} or "all";
# Converts all 'archives' keys in a repo spec to fetchurl calls.
fetchArchives =
attrSet:
lib.attrsets.mapAttrsRecursive (
path: value:
if (builtins.elemAt path (builtins.length path - 1)) == "archives" then
let
validArchives = builtins.filter (
archive:
let
isTargetOs =
if builtins.hasAttr "os" archive then archive.os == os || archive.os == "all" else true;
isTargetArch =
if builtins.hasAttr "arch" archive then archive.arch == arch || archive.arch == "all" else true;
in
isTargetOs && isTargetArch
) value;
packageInfo = lib.attrByPath (lib.sublist 0 (builtins.length path - 1) path) null attrSet;
in
lib.optionals (builtins.length validArchives > 0) (
lib.last (
map (
archive:
(fetchurl {
inherit (archive) url sha1;
inherit meta;
passthru = {
info = packageInfo;
};
}).overrideAttrs
(prev: {
# fetchurl won't generate the correct filename if we specify pname and version,
# and we still want the version attribute to show up in search, so specify these in an override
pname = packageInfo.name;
version = packageInfo.revision;
})
) validArchives
)
)
else
value
) attrSet;
# Converts the repo attrset into fetch calls.
allArchives = {
packages = fetchArchives repo.packages;
system-images = fetchArchives repo.images;
addons = fetchArchives repo.addons;
extras = fetchArchives repo.extras;
};
# Lift the archives to the package level for easy search,
# and add recurseIntoAttrs to all of them.
allPackages =
let
liftedArchives = lib.attrsets.mapAttrsRecursiveCond (value: !(builtins.hasAttr "archives" value)) (
path: value:
if (value.archives or null) != null && (value.archives or [ ]) != [ ] then
lib.dontRecurseIntoAttrs value.archives
else
null
) allArchives;
# Creates a version key from a name.
# Converts things like 'extras;google;auto' to 'extras-google-auto'
toVersionKey =
name:
let
normalizedName = lib.replaceStrings [ ";" "." ] [ "-" "_" ] name;
versionParts = lib.match "^([0-9][0-9\\.]*)(.*)$" normalizedName;
in
if versionParts == null then normalizedName else "v" + lib.concatStrings versionParts;
recurse = lib.mapAttrs' (
name: value:
if builtins.isAttrs value && (value.recurseForDerivations or true) then
lib.nameValuePair (toVersionKey name) (lib.recurseIntoAttrs (recurse value))
else
lib.nameValuePair (toVersionKey name) value
);
in
lib.recurseIntoAttrs (recurse liftedArchives);
# Converts a license name to a list of license texts.
mkLicenses = licenseName: repo.licenses.${licenseName};
# Converts a list of license names to a flattened list of license texts.
# Just used for displaying licenses.
mkLicenseTexts =
licenseNames:
lib.lists.flatten (
map (
licenseName: map (licenseText: "--- ${licenseName} ---\n${licenseText}") (mkLicenses licenseName)
) licenseNames
);
# Converts a license name to a list of license hashes.
mkLicenseHashes =
licenseName: map (licenseText: builtins.hashString "sha1" licenseText) (mkLicenses licenseName);
# The list of all license names we're accepting. Put android-sdk-license there
# by default.
licenseNames = lib.lists.unique (
[
"android-sdk-license"
]
++ extraLicenses
);
# Returns true if the given version exists.
hasVersion =
packages: package: version:
lib.hasAttrByPath [ package (toString version) ] packages;
# Displays a nice error message that includes the available options if a version doesn't exist.
checkVersion =
packages: package: version:
if hasVersion packages package version then
packages.${package}.${toString version}
else
throw ''
The version ${toString version} is missing in package ${package}.
The only available versions are ${
builtins.concatStringsSep ", " (builtins.attrNames packages.${package})
}.
'';
# Returns true if we should link the specified plugins.
shouldLink =
check: packages:
assert builtins.isList packages;
if check == true then
true
else if check == false then
false
else if check == "if-supported" then
let
hasSrc =
package: package.src != null && (builtins.isList package.src -> builtins.length package.src > 0);
in
packages != [ ] && lib.all hasSrc packages
else
throw "Invalid argument ${toString check}; use true, false, or if-supported";
# Function that automatically links all plugins for which multiple versions can coexist
linkPlugins =
{
name,
plugins,
check ? true,
}:
lib.optionalString (shouldLink check plugins) ''
mkdir -p ${name}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
'') plugins}
'';
# Function that automatically links all NDK plugins.
linkNdkPlugins =
{
name,
plugins,
rootName ? name,
check ? true,
}:
lib.optionalString (shouldLink check plugins) ''
mkdir -p ${rootName}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name} ${rootName}/${plugin.version}
'') plugins}
'';
# Function that automatically links the default NDK plugin.
linkNdkPlugin =
{
name,
plugin,
check,
}:
lib.optionalString (shouldLink check [ plugin ]) ''
ln -s ${plugin}/libexec/android-sdk/${name} ${name}
'';
# Function that automatically links a plugin for which only one version exists
linkPlugin =
{
name,
plugin,
check ? true,
}:
lib.optionalString (shouldLink check [ plugin ]) ''
ln -s ${plugin}/libexec/android-sdk/${name} ${name}
'';
linkSystemImages =
{ images, check }:
lib.optionalString (shouldLink check images) ''
mkdir -p system-images
${lib.concatMapStrings (system-image: ''
apiVersion=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*))
type=$(basename $(echo ${system-image}/libexec/android-sdk/system-images/*/*))
mkdir -p system-images/$apiVersion
ln -s ${system-image}/libexec/android-sdk/system-images/$apiVersion/$type system-images/$apiVersion/$type
'') images}
'';
# Links all plugins related to a requested platform
linkPlatformPlugins =
{
name,
plugins,
check,
}:
lib.optionalString (shouldLink check plugins) ''
mkdir -p ${name}
${lib.concatMapStrings (plugin: ''
ln -s ${plugin}/libexec/android-sdk/${name}/* ${name}
'') plugins}
''; # */
in
lib.recurseIntoAttrs rec {
deployAndroidPackages = callPackage ./deploy-androidpackages.nix {
inherit
stdenv
lib
mkLicenses
meta
os
arch
;
};
deployAndroidPackage = (
{
package,
buildInputs ? [ ],
patchInstructions ? "",
meta ? { },
...
}@args:
let
extraParams = removeAttrs args [
"package"
"os"
"arch"
"buildInputs"
"patchInstructions"
];
in
deployAndroidPackages (
{
inherit buildInputs;
packages = [ package ];
patchesInstructions = {
"${package.name}" = patchInstructions;
};
}
// extraParams
)
);
all = allPackages;
platform-tools = callPackage ./platform-tools.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = checkVersion allArchives.packages "platform-tools" (
parseVersion repo "platform-tools" platformToolsVersion
);
};
tools = callPackage ./tools.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = checkVersion allArchives.packages "tools" (parseVersion repo "tools" toolsVersion);
postInstall = ''
${linkPlugin {
name = "platform-tools";
plugin = platform-tools;
}}
${linkPlugin {
name = "emulator";
plugin = emulator;
check = includeEmulator;
}}
'';
};
build-tools = map (
version:
callPackage ./build-tools.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = checkVersion allArchives.packages "build-tools" version;
postInstall = ''
${linkPlugin {
name = "tools";
plugin = tools;
check = toolsVersion != null;
}}
'';
}
) (parseVersions repo "build-tools" buildToolsVersions);
emulator = callPackage ./emulator.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = checkVersion allArchives.packages "emulator" (
parseVersion repo "emulator" emulatorVersion
);
postInstall = ''
${linkSystemImages {
images = system-images;
check = includeSystemImages;
}}
'';
};
platformVersions = platformVersions';
platforms = map (
version:
deployAndroidPackage {
package = checkVersion allArchives.packages "platforms" version;
}
) platformVersions';
sources = map (
version:
deployAndroidPackage {
package = checkVersion allArchives.packages "sources" version;
}
) platformVersions';
system-images = lib.flatten (
map (
apiVersion:
map (
type:
# Deploy all system images with the same systemImageType in one derivation to avoid the `null` problem below
# with avdmanager when trying to create an avd!
#
# ```
# $ yes "" | avdmanager create avd --force --name testAVD --package 'system-images;android-33;google_apis;x86_64'
# Error: Package path is not valid. Valid system image paths are:
# null
# ```
let
availablePackages =
map (abiVersion: allArchives.system-images.${toString apiVersion}.${type}.${abiVersion})
(
builtins.filter (
abiVersion: lib.hasAttrByPath [ (toString apiVersion) type abiVersion ] allArchives.system-images
) abiVersions
);
instructions = builtins.listToAttrs (
map (package: {
name = package.name;
value = lib.optionalString (lib.hasPrefix "google_apis" type) ''
# Patch 'google_apis' system images so they're recognized by the sdk.
# Without this, `android list targets` shows 'Tag/ABIs : no ABIs' instead
# of 'Tag/ABIs : google_apis*/*' and the emulator fails with an ABI-related error.
sed -i '/^Addon.Vendor/d' source.properties
'';
}) availablePackages
);
in
lib.optionals (availablePackages != [ ]) (deployAndroidPackages {
packages = availablePackages;
patchesInstructions = instructions;
})
) systemImageTypes
) platformVersions'
);
cmake = map (
version:
callPackage ./cmake.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = checkVersion allArchives.packages "cmake" version;
}
) (parseVersions repo "cmake" cmakeVersions);
# All NDK bundles.
ndk-bundles =
let
# Creates a NDK bundle.
makeNdkBundle =
package:
callPackage ./ndk-bundle {
inherit
deployAndroidPackage
os
arch
platform-tools
meta
package
;
};
in
lib.flatten (
map (
version:
let
package = makeNdkBundle (
allArchives.packages.ndk-bundle.${version} or allArchives.packages.ndk.${version}
);
in
lib.optional (shouldLink includeNDK [ package ]) package
) (parseVersions repo "ndk" ndkVersions)
);
# The "default" NDK bundle.
ndk-bundle = if ndk-bundles == [ ] then null else lib.head ndk-bundles;
# Makes a Google API bundle from supported versions.
google-apis = map (
version:
deployAndroidPackage {
package = (checkVersion allArchives "addons" version).google_apis;
}
) (lib.filter (hasVersion allArchives "addons") platformVersions');
# Makes a Google TV addons bundle from supported versions.
google-tv-addons = map (
version:
deployAndroidPackage {
package = (checkVersion allArchives "addons" version).google_tv_addon;
}
) (lib.filter (hasVersion allArchives "addons") platformVersions');
cmdline-tools-package = checkVersion allArchives.packages "cmdline-tools" (
parseVersion repo "cmdline-tools" cmdLineToolsVersion
);
# This derivation deploys the tools package and symlinks all the desired
# plugins that we want to use. If the license isn't accepted, prints all the licenses
# requested and throws.
androidsdk = callPackage ./cmdline-tools.nix {
inherit
deployAndroidPackage
os
arch
meta
;
package = cmdline-tools-package;
postInstall =
if !licenseAccepted then
throw ''
${builtins.concatStringsSep "\n\n" (mkLicenseTexts licenseNames)}
You must accept the following licenses:
${lib.concatMapStringsSep "\n" (str: " - ${str}") licenseNames}
a)
by setting nixpkgs config option 'android_sdk.accept_license = true;'.
b)
by an environment variable for a single invocation of the nix tools.
$ export NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1
''
else
''
# Symlink all requested plugins
${linkPlugin {
name = "platform-tools";
plugin = platform-tools;
}}
${linkPlugin {
name = "tools";
plugin = tools;
check = toolsVersion != null;
}}
${linkPlugins {
name = "build-tools";
plugins = build-tools;
}}
${linkPlugin {
name = "emulator";
plugin = emulator;
check = includeEmulator;
}}
${linkPlugins {
name = "platforms";
plugins = platforms;
}}
${linkPlatformPlugins {
name = "sources";
plugins = sources;
check = includeSources;
}}
${linkPlugins {
name = "cmake";
plugins = cmake;
check = includeCmake;
}}
${linkNdkPlugins {
name = "ndk-bundle";
rootName = "ndk";
plugins = ndk-bundles;
check = includeNDK;
}}
${linkNdkPlugin {
name = "ndk-bundle";
plugin = ndk-bundle;
check = includeNDK;
}}
${linkSystemImages {
images = system-images;
check = includeSystemImages;
}}
${linkPlatformPlugins {
name = "add-ons";
plugins = google-apis;
check = useGoogleAPIs;
}}
${linkPlatformPlugins {
name = "add-ons";
plugins = google-tv-addons;
check = useGoogleTVAddOns;
}}
# Link extras
${lib.concatMapStrings (
identifier:
let
package = allArchives.extras.${identifier};
path = package.path;
extras = callPackage ./extras.nix {
inherit
deployAndroidPackage
package
os
arch
meta
;
};
in
''
targetDir=$(dirname ${path})
mkdir -p $targetDir
ln -s ${extras}/libexec/android-sdk/${path} $targetDir
''
) includeExtras}
# Expose common executables in bin/
mkdir -p $out/bin
for i in ${platform-tools}/bin/*; do
ln -s $i $out/bin
done
${lib.optionalString (shouldLink includeEmulator [ emulator ]) ''
for i in ${emulator}/bin/*; do
ln -s $i $out/bin
done
''}
find $ANDROID_SDK_ROOT/${cmdline-tools-package.path}/bin -type f -executable | while read i; do
ln -s $i $out/bin
done
# Write licenses
mkdir -p licenses
${lib.concatMapStrings (
licenseName:
let
licenseHashes = builtins.concatStringsSep "\n" (mkLicenseHashes licenseName);
licenseHashFile = writeText "androidenv-${licenseName}" licenseHashes;
in
''
ln -s ${licenseHashFile} licenses/${licenseName}
''
) licenseNames}
'';
};
}

View File

@@ -0,0 +1,41 @@
{
lib,
pkgs ? import <nixpkgs> { },
licenseAccepted ? pkgs.callPackage ./license.nix { },
}:
lib.recurseIntoAttrs rec {
composeAndroidPackages = pkgs.callPackage ./compose-android-packages.nix {
inherit licenseAccepted meta;
};
buildApp = pkgs.callPackage ./build-app.nix {
inherit composeAndroidPackages meta;
};
emulateApp = pkgs.callPackage ./emulate-app.nix {
inherit composeAndroidPackages meta;
};
androidPkgs = composeAndroidPackages {
# Support roughly the last 5 years of Android packages and system images by default in nixpkgs.
numLatestPlatformVersions = 5;
includeEmulator = "if-supported";
includeSystemImages = "if-supported";
includeNDK = "if-supported";
};
test-suite = pkgs.callPackage ./test-suite.nix {
inherit meta;
};
inherit (test-suite) passthru;
meta = {
homepage = "https://developer.android.com/tools";
description = "Android SDK tools, packaged in Nixpkgs";
license = lib.licenses.unfree;
platforms = lib.platforms.all;
teams = [ lib.teams.android ];
};
}

View File

@@ -0,0 +1,159 @@
{
stdenv,
lib,
unzip,
mkLicenses,
os,
arch,
meta,
}:
{
packages,
nativeBuildInputs ? [ ],
buildInputs ? [ ],
patchesInstructions ? { },
...
}@args:
let
extraParams = removeAttrs args [
"packages"
"os"
"buildInputs"
"nativeBuildInputs"
"patchesInstructions"
];
sortedPackages = builtins.sort (x: y: builtins.lessThan x.name y.name) packages;
mkXmlAttrs =
attrs: lib.concatStrings (lib.mapAttrsToList (name: value: " ${name}=\"${value}\"") attrs);
mkXmlValues =
attrs:
lib.concatStrings (
lib.mapAttrsToList (
name: value:
let
tag = builtins.head (builtins.match "([^:]+).*" name);
in
if builtins.typeOf value == "string" then "<${tag}>${value}</${tag}>" else mkXmlDoc name value
) attrs
);
mkXmlDoc =
name: doc:
let
tag = builtins.head (builtins.match "([^:]+).*" name);
hasXmlAttrs = builtins.hasAttr "element-attributes" doc;
xmlValues = removeAttrs doc [ "element-attributes" ];
hasXmlValues = builtins.length (builtins.attrNames xmlValues) > 0;
in
if hasXmlAttrs && hasXmlValues then
"<${tag}${mkXmlAttrs doc.element-attributes}>${mkXmlValues xmlValues}</${tag}>"
else if hasXmlAttrs && !hasXmlValues then
"<${tag}${mkXmlAttrs doc.element-attributes}/>"
else if !hasXmlAttrs && hasXmlValues then
"<${tag}>${mkXmlValues xmlValues}</${tag}>"
else
"<${tag}/>";
mkXmlPackage = package: ''
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:repository
xmlns:ns2="http://schemas.android.com/repository/android/common/02"
xmlns:ns3="http://schemas.android.com/repository/android/common/01"
xmlns:ns4="http://schemas.android.com/repository/android/generic/01"
xmlns:ns5="http://schemas.android.com/repository/android/generic/02"
xmlns:ns6="http://schemas.android.com/sdk/android/repo/addon2/01"
xmlns:ns7="http://schemas.android.com/sdk/android/repo/addon2/02"
xmlns:ns8="http://schemas.android.com/sdk/android/repo/addon2/03"
xmlns:ns9="http://schemas.android.com/sdk/android/repo/repository2/01"
xmlns:ns10="http://schemas.android.com/sdk/android/repo/repository2/02"
xmlns:ns11="http://schemas.android.com/sdk/android/repo/repository2/03"
xmlns:ns12="http://schemas.android.com/sdk/android/repo/sys-img2/03"
xmlns:ns13="http://schemas.android.com/sdk/android/repo/sys-img2/02"
xmlns:ns14="http://schemas.android.com/sdk/android/repo/sys-img2/01"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<license id="${package.license}" type="text">${lib.concatStringsSep "---" (mkLicenses package.license)}</license>
<localPackage path="${builtins.replaceStrings [ "/" ] [ ";" ] package.path}" obsolete="${
if (lib.hasAttrByPath [ "obsolete" ] package) then package.obsolete else "false"
}">
${mkXmlDoc "type-details" package.type-details}
${mkXmlDoc "revision" package.revision-details}
${lib.optionalString (lib.hasAttrByPath [ "dependencies" ] package) (
mkXmlDoc "dependencies" package.dependencies
)}
<display-name>${package.displayName}</display-name>
<uses-license ref="${package.license}"/>
</localPackage>
</ns2:repository>
'';
in
stdenv.mkDerivation (
{
inherit buildInputs;
pname = "android-sdk-${lib.concatMapStringsSep "-" (package: package.name) sortedPackages}";
version = lib.concatMapStringsSep "-" (package: package.revision) sortedPackages;
src = lib.flatten (map (package: package.archives) packages);
inherit os arch;
nativeBuildInputs = [ unzip ] ++ nativeBuildInputs;
preferLocalBuild = true;
unpackPhase = ''
runHook preUnpack
if [ -z "$src" ]; then
echo "$pname did not have any sources available for os=$os, arch=$arch." >&2
echo "Are packages available for this architecture?" >&2
exit 1
fi
buildDir=$PWD
i=0
for srcArchive in $src; do
extractedZip="extractedzip-$i"
i=$((i+1))
cd "$buildDir"
mkdir "$extractedZip"
cd "$extractedZip"
unpackFile "$srcArchive"
done
runHook postUnpack
'';
installPhase = ''
runHook preInstall
''
+ lib.concatStrings (
lib.imap0 (i: package: ''
cd $buildDir/extractedzip-${toString i}
# Most Android Zip packages have a root folder, but some don't. We unpack
# the zip file in a folder and we try to discover whether it has a single root
# folder. If this is the case, we adjust the current working folder.
if [ "$(find . -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 1 ]; then
cd "$(find . -mindepth 1 -maxdepth 1 -type d)"
fi
extractedZip="$PWD"
packageBaseDir=$out/libexec/android-sdk/${package.path}
mkdir -p $packageBaseDir
cd $packageBaseDir
cp -a $extractedZip/* .
${patchesInstructions.${package.name}}
if [ ! -f $packageBaseDir/package.xml ]; then
cat << EOF > $packageBaseDir/package.xml
${mkXmlPackage package}
EOF
fi
'') packages
)
+ ''
runHook postInstall
'';
# Some executables that have been patched with patchelf may not work any longer after they have been stripped.
dontStrip = true;
dontPatchELF = true;
dontAutoPatchelf = true;
inherit meta;
}
// extraParams
)

View File

@@ -0,0 +1,201 @@
{
composeAndroidPackages,
stdenv,
lib,
runtimeShell,
meta,
}:
{
name,
app ? null,
platformVersion ? "35",
abiVersion ? "x86",
systemImageType ? "default",
enableGPU ? false, # Enable GPU acceleration. It's deprecated, instead use `configOptions` below.
configOptions ? (
# List of options to add in config.ini
lib.optionalAttrs enableGPU (
lib.warn "enableGPU argument is deprecated and will be removed; use configOptions instead" {
"hw.gpu.enabled" = "yes";
}
)
),
extraAVDFiles ? [ ],
package ? null,
activity ? null,
androidUserHome ? null,
avdHomeDir ? null, # Support old variable with non-standard naming!
androidAvdHome ? avdHomeDir,
deviceName ? "device",
sdkExtraArgs ? { },
androidAvdFlags ? null,
androidEmulatorFlags ? null,
}:
let
sdkArgs = {
includeEmulator = true;
includeSystemImages = true;
}
// sdkExtraArgs
// {
cmdLineToolsVersion = "8.0";
platformVersions = [ platformVersion ];
systemImageTypes = [ systemImageType ];
abiVersions = [ abiVersion ];
};
sdk = (composeAndroidPackages sdkArgs).androidsdk;
in
stdenv.mkDerivation {
inherit name;
buildCommand = ''
mkdir -p $out/bin
cat > $out/bin/run-test-emulator << "EOF"
#!${runtimeShell} -e
# We need a TMPDIR
if [ "$TMPDIR" = "" ]
then
export TMPDIR=/tmp
fi
${
if androidUserHome == null then
''
# Store the virtual devices somewhere else, instead of polluting a user's HOME directory
export ANDROID_USER_HOME=$(mktemp -d $TMPDIR/nix-android-user-home-XXXX)
''
else
''
mkdir -p "${androidUserHome}"
export ANDROID_USER_HOME="${androidUserHome}"
''
}
${
if androidAvdHome == null then
''
export ANDROID_AVD_HOME=$ANDROID_USER_HOME/avd
''
else
''
mkdir -p "${androidAvdHome}"
export ANDROID_AVD_HOME="${androidAvdHome}"
''
}
# We need to specify the location of the Android SDK root folder
export ANDROID_SDK_ROOT=${sdk}/libexec/android-sdk
${lib.optionalString (androidAvdFlags != null) ''
# If NIX_ANDROID_AVD_FLAGS is empty
if [[ -z "$NIX_ANDROID_AVD_FLAGS" ]]; then
NIX_ANDROID_AVD_FLAGS="${androidAvdFlags}"
fi
''}
${lib.optionalString (androidEmulatorFlags != null) ''
# If NIX_ANDROID_EMULATOR_FLAGS is empty
if [[ -z "$NIX_ANDROID_EMULATOR_FLAGS" ]]; then
NIX_ANDROID_EMULATOR_FLAGS="${androidEmulatorFlags}"
fi
''}
# We have to look for a free TCP port
echo "Looking for a free TCP port in range 5554-5584" >&2
for i in $(seq 5554 2 5584)
do
if [ -z "$(${sdk}/bin/adb devices | grep emulator-$i)" ]
then
port=$i
break
fi
done
if [ -z "$port" ]
then
echo "Unfortunately, the emulator port space is exhausted!" >&2
exit 1
else
echo "We have a free TCP port: $port" >&2
fi
export ANDROID_SERIAL="emulator-$port"
# Create a virtual android device for testing if it does not exist
if [ "$(${sdk}/bin/avdmanager list avd | grep 'Name: ${deviceName}')" = "" ]
then
# Create a virtual android device
yes "" | ${sdk}/bin/avdmanager create avd --force -n ${deviceName} -k "system-images;android-${platformVersion};${systemImageType};${abiVersion}" -p $ANDROID_AVD_HOME/${deviceName}.avd $NIX_ANDROID_AVD_FLAGS
${builtins.concatStringsSep "\n" (
lib.mapAttrsToList (configKey: configValue: ''
echo "${configKey} = ${configValue}" >> $ANDROID_AVD_HOME/${deviceName}.avd/config.ini
'') configOptions
)}
${lib.concatMapStrings (extraAVDFile: ''
ln -sf ${extraAVDFile} $ANDROID_AVD_HOME/${deviceName}.avd
'') extraAVDFiles}
fi
# Launch the emulator
echo "\nLaunch the emulator"
$ANDROID_SDK_ROOT/emulator/emulator -avd ${deviceName} -no-boot-anim -port $port $NIX_ANDROID_EMULATOR_FLAGS &
# Wait until the device has completely booted
echo "Waiting until the emulator has booted the ${deviceName} and the package manager is ready..." >&2
${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port wait-for-device
echo "Device state has been reached" >&2
while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop dev.bootcomplete | grep 1)" ]
do
sleep 5
done
echo "dev.bootcomplete property is 1" >&2
#while [ -z "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell getprop sys.boot_completed | grep 1)" ]
#do
#sleep 5
#done
#echo "sys.boot_completed property is 1" >&2
echo "ready" >&2
${lib.optionalString (app != null) ''
# Install the App through the debugger, if it has not been installed yet
if [ -z "${package}" ] || [ "$(${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell pm list packages | grep package:${package})" = "" ]
then
if [ -d "${app}" ]
then
appPath="$(echo ${app}/*.apk)"
else
appPath="${app}"
fi
${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port install "$appPath"
fi
# Start the application
${lib.optionalString (package != null && activity != null) ''
${sdk}/libexec/android-sdk/platform-tools/adb -s emulator-$port shell am start -a android.intent.action.MAIN -n ${package}/${activity}
''}
''}
EOF
chmod +x $out/bin/run-test-emulator
'';
meta = meta // {
mainProgram = "run-test-emulator";
};
}

View File

@@ -0,0 +1,98 @@
{
deployAndroidPackage,
lib,
stdenv,
package,
os,
arch,
autoPatchelfHook,
makeWrapper,
pkgs,
pkgsi686Linux,
postInstall,
meta,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = [ makeWrapper ] ++ lib.optionals (os == "linux") [ autoPatchelfHook ];
buildInputs =
lib.optionals (os == "linux") (
with pkgs;
[
glibc
libcxx
libGL
libpulseaudio
libtiff
libuuid
zlib
libbsd
ncurses5
libdrm
stdenv.cc.cc
expat
freetype
nss
nspr
alsa-lib
waylandpp.lib
]
)
++ (with pkgs.xorg; [
libX11
libXext
libXdamage
libXfixes
libxcb
libXcomposite
libXcursor
libXi
libXrender
libXtst
libICE
libSM
libxkbfile
libxshmfence
])
++ lib.optional (os == "linux" && stdenv.isx86_64) pkgsi686Linux.glibc;
patchInstructions =
(lib.optionalString (os == "linux") ''
addAutoPatchelfSearchPath $packageBaseDir/lib
addAutoPatchelfSearchPath $packageBaseDir/lib64
addAutoPatchelfSearchPath $packageBaseDir/lib64/qt/lib
# autoPatchelf is not detecting libuuid :(
addAutoPatchelfSearchPath ${pkgs.libuuid.out}/lib
# This library is linked against a version of libtiff that nixpkgs doesn't have
for file in $out/libexec/android-sdk/emulator/*/qt/plugins/imageformats/libqtiffAndroidEmu.so; do
patchelf --replace-needed libtiff.so.5 libtiff.so "$file" || true
done
autoPatchelf $out
# Wrap emulator so that it can load required libraries at runtime
wrapProgram $out/libexec/android-sdk/emulator/emulator \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [
pkgs.dbus
pkgs.systemd
]
} \
--set QT_XKB_CONFIG_ROOT ${pkgs.xkeyboard_config}/share/X11/xkb \
--set QTCOMPOSE ${pkgs.xorg.libX11.out}/share/X11/locale
'')
+ ''
mkdir -p $out/bin
cd $out/bin
find $out/libexec/android-sdk/emulator -type f -executable -mindepth 1 -maxdepth 1 | while read i; do
ln -s $i
done
cd $out/libexec/android-sdk
${postInstall}
'';
dontMoveLib64 = true;
inherit meta;
}

View File

@@ -0,0 +1,60 @@
{
# If you want to use the in-tree version of nixpkgs:
pkgs ? import ../../../../.. {
config.allowUnfree = true;
},
licenseAccepted ? pkgs.callPackage ../license.nix { },
}:
# Tests IFD with androidenv. Needs a folder of `../xml` in your local tree;
# use ../fetchrepo.sh to produce it.
let
androidEnv = pkgs.callPackage ./.. {
inherit pkgs licenseAccepted;
};
sdkArgs = {
repoXmls = {
packages = [ ../xml/repository2-3.xml ];
images = [
../xml/android-sys-img2-3.xml
../xml/android-tv-sys-img2-3.xml
../xml/google_apis-sys-img2-3.xml
../xml/google_apis_playstore-sys-img2-3.xml
../xml/android-wear-sys-img2-3.xml
../xml/android-wear-cn-sys-img2-3.xml
../xml/android-automotive-sys-img2-3.xml
];
addons = [ ../xml/addon2-3.xml ];
};
};
androidComposition = androidEnv.composeAndroidPackages sdkArgs;
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
jdk = pkgs.jdk;
in
pkgs.mkShell {
name = "androidenv-example-ifd-demo";
packages = [
androidSdk
platformTools
jdk
];
LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = jdk.home;
# Note: ANDROID_HOME is deprecated. Use ANDROID_SDK_ROOT.
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
shellHook = ''
# Write out local.properties for Android Studio.
cat <<EOF > local.properties
# This file was automatically generated by nix-shell.
sdk.dir=$ANDROID_SDK_ROOT
EOF
'';
}

View File

@@ -0,0 +1,202 @@
{
# To test your changes in androidEnv run `nix-shell android-sdk-with-emulator-shell.nix`
# If you copy this example out of nixpkgs, use these lines instead of the next.
# This example pins nixpkgs: https://nix.dev/tutorials/first-steps/towards-reproducibility-pinning-nixpkgs.html
/*
nixpkgsSource ? (fetchTarball {
name = "nixpkgs-20.09";
url = "https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz";
sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy";
}),
pkgs ? import nixpkgsSource {
config.allowUnfree = true;
},
*/
# If you want to use the in-tree version of nixpkgs:
pkgs ? import ../../../../.. {
config.allowUnfree = true;
},
# You probably need to set it to true to express consent.
licenseAccepted ? pkgs.callPackage ../license.nix { },
}:
# Copy this file to your Android project.
let
# If you copy this example out of nixpkgs, something like this will work:
/*
androidEnvNixpkgs = fetchTarball {
name = "androidenv";
url = "https://github.com/NixOS/nixpkgs/archive/<fill me in from Git>.tar.gz";
sha256 = "<fill me in with nix-prefetch-url --unpack>";
};
androidEnv = pkgs.callPackage "${androidEnvNixpkgs}/pkgs/development/mobile/androidenv" {
inherit pkgs;
licenseAccepted = true;
};
*/
# Otherwise, just use the in-tree androidenv:
androidEnv = pkgs.callPackage ./.. {
inherit pkgs licenseAccepted;
};
emulatorSupported = pkgs.stdenv.hostPlatform.isx86_64 || pkgs.stdenv.hostPlatform.isDarwin;
sdkArgs = {
includeSystemImages = true;
includeEmulator = "if-supported";
# Accepting more licenses declaratively:
extraLicenses = [
# Already accepted for you with the global accept_license = true or
# licenseAccepted = true on androidenv.
# "android-sdk-license"
# These aren't, but are useful for more uncommon setups.
"android-sdk-preview-license"
"android-googletv-license"
"android-sdk-arm-dbt-license"
"google-gdk-license"
"intel-android-extra-license"
"intel-android-sysimage-license"
"mips-android-sysimage-license"
];
};
androidComposition = androidEnv.composeAndroidPackages sdkArgs;
androidEmulator = androidEnv.emulateApp {
name = "android-sdk-emulator-demo";
configOptions = {
"hw.keyboard" = "yes";
};
sdkExtraArgs = sdkArgs;
};
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions;
jdk = pkgs.jdk;
in
pkgs.mkShell rec {
name = "androidenv-demo";
packages = [
androidSdk
platformTools
androidEmulator
jdk
];
LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = jdk.home;
# Note: ANDROID_HOME is deprecated. Use ANDROID_SDK_ROOT.
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";
shellHook = ''
# Write out local.properties for Android Studio.
cat <<EOF > local.properties
# This file was automatically generated by nix-shell.
sdk.dir=$ANDROID_SDK_ROOT
ndk.dir=$ANDROID_NDK_ROOT
EOF
'';
passthru.tests = {
shell-with-emulator-sdkmanager-packages-test =
pkgs.runCommand "shell-with-emulator-sdkmanager-packages-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
echo "installed_packages_section: ''${installed_packages_section}"
packages=(
"build-tools" "cmdline-tools" \
"platform-tools" "platforms;android-${toString latestSdk}" \
"system-images;android-${toString latestSdk};google_apis;x86_64"
)
${pkgs.lib.optionalString emulatorSupported ''packages+=("emulator")''}
for package in "''${packages[@]}"; do
if [[ ! $installed_packages_section =~ "$package" ]]; then
echo "$package package was not installed."
exit 1
fi
done
touch "$out"
'';
shell-with-emulator-sdkmanager-excluded-packages-test =
pkgs.runCommand "shell-with-emulator-sdkmanager-excluded-packages-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
excluded_packages=(ndk)
for x in $(seq 1 ${toString latestSdk}); do
excluded_packages+=(
"platforms;android-$x"
"sources;android-$x"
"system-images;android-$x"
)
done
for package in "''${excluded_packages[@]}"; do
if [[ $installed_packages_section =~ ^"$package"$ ]]; then
echo "$package package was installed, while it was excluded!"
exit 1
fi
done
touch "$out"
'';
shell-with-emulator-avdmanager-create-avd-test =
pkgs.runCommand "shell-with-emulator-avdmanager-create-avd-test"
{
nativeBuildInputs = [
androidSdk
androidEmulator
jdk
];
}
(
pkgs.lib.optionalString emulatorSupported ''
export ANDROID_USER_HOME=$PWD/.android
mkdir -p $ANDROID_USER_HOME
avdmanager delete avd -n testAVD || true
echo "" | avdmanager create avd --force --name testAVD --package 'system-images;android-${toString latestSdk};google_apis;x86_64'
result=$(avdmanager list avd)
if [[ ! $result =~ "Name: testAVD" ]]; then
echo "avdmanager couldn't create the avd! The output is :''${result}"
exit 1
fi
avdmanager delete avd -n testAVD || true
''
+ ''
touch $out
''
);
};
}

View File

@@ -0,0 +1,153 @@
{
# To test your changes in androidEnv run `nix-shell android-sdk-with-emulator-shell.nix`
# If you copy this example out of nixpkgs, use these lines instead of the next.
# This example pins nixpkgs: https://nix.dev/tutorials/first-steps/towards-reproducibility-pinning-nixpkgs.html
/*
nixpkgsSource ? (fetchTarball {
name = "nixpkgs-20.09";
url = "https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz";
sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy";
}),
pkgs ? import nixpkgsSource {
config.allowUnfree = true;
},
*/
# If you want to use the in-tree version of nixpkgs:
pkgs ? import ../../../../.. {
config.allowUnfree = true;
},
licenseAccepted ? pkgs.callPackage ../license.nix { },
}:
# Copy this file to your Android project.
let
# If you copy this example out of nixpkgs, something like this will work:
/*
androidEnvNixpkgs = fetchTarball {
name = "androidenv";
url = "https://github.com/NixOS/nixpkgs/archive/<fill me in from Git>.tar.gz";
sha256 = "<fill me in with nix-prefetch-url --unpack>";
};
androidEnv = pkgs.callPackage "${androidEnvNixpkgs}/pkgs/development/mobile/androidenv" {
inherit pkgs;
licenseAccepted = true;
};
*/
# Otherwise, just use the in-tree androidenv:
androidEnv = pkgs.callPackage ./.. {
inherit pkgs licenseAccepted;
};
sdkArgs = {
includeNDK = false;
includeSystemImages = false;
includeEmulator = false;
# Accepting more licenses declaratively:
extraLicenses = [
# Already accepted for you with the global accept_license = true or
# licenseAccepted = true on androidenv.
# "android-sdk-license"
# These aren't, but are useful for more uncommon setups.
"android-sdk-preview-license"
"android-googletv-license"
"android-sdk-arm-dbt-license"
"google-gdk-license"
"intel-android-extra-license"
"intel-android-sysimage-license"
"mips-android-sysimage-license"
];
};
androidComposition = androidEnv.composeAndroidPackages sdkArgs;
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions;
jdk = pkgs.jdk;
in
pkgs.mkShell {
name = "androidenv-example-without-emulator-demo";
packages = [
androidSdk
platformTools
jdk
];
LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = jdk.home;
# Note: ANDROID_HOME is deprecated. Use ANDROID_SDK_ROOT.
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
shellHook = ''
# Write out local.properties for Android Studio.
cat <<EOF > local.properties
# This file was automatically generated by nix-shell.
sdk.dir=$ANDROID_SDK_ROOT
EOF
'';
passthru.tests = {
shell-without-emulator-sdkmanager-packages-test =
pkgs.runCommand "shell-without-emulator-sdkmanager-packages-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
echo "installed_packages_section: ''${installed_packages_section}"
packages=(
"build-tools" "cmdline-tools" \
"platform-tools" "platforms;android-${toString latestSdk}"
)
for package in "''${packages[@]}"; do
if [[ ! $installed_packages_section =~ "$package" ]]; then
echo "$package package was not installed."
exit 1
fi
done
touch "$out"
'';
shell-without-emulator-sdkmanager-excluded-packages-test =
pkgs.runCommand "shell-without-emulator-sdkmanager-excluded-packages-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
excluded_packages=(
"emulator" "ndk"
)
for package in "''${excluded_packages[@]}"; do
if [[ $installed_packages_section =~ "$package" ]]; then
echo "$package package was installed, while it was excluded!"
exit 1
fi
done
touch "$out"
'';
};
}

View File

@@ -0,0 +1,210 @@
{
# If you copy this example out of nixpkgs, use these lines instead of the next.
# This example pins nixpkgs: https://nix.dev/tutorials/first-steps/towards-reproducibility-pinning-nixpkgs.html
/*
nixpkgsSource ? (fetchTarball {
name = "nixpkgs-20.09";
url = "https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz";
sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy";
}),
pkgs ? import nixpkgsSource {
config.allowUnfree = true;
},
*/
# If you want to use the in-tree version of nixpkgs:
pkgs ? import ../../../../.. {
config.allowUnfree = true;
},
# You probably need to set it to true to express consent.
licenseAccepted ? pkgs.callPackage ../license.nix { },
}:
# Copy this file to your Android project.
let
# If you copy this example out of nixpkgs, something like this will work:
/*
androidEnvNixpkgs = fetchTarball {
name = "androidenv";
url = "https://github.com/NixOS/nixpkgs/archive/<fill me in from Git>.tar.gz";
sha256 = "<fill me in with nix-prefetch-url --unpack>";
};
androidEnv = pkgs.callPackage "${androidEnvNixpkgs}/pkgs/development/mobile/androidenv" {
inherit pkgs;
licenseAccepted = true;
};
*/
# Otherwise, just use the in-tree androidenv:
androidEnv = pkgs.callPackage ./.. {
inherit pkgs licenseAccepted;
};
# The head unit only works on these platforms
includeAuto = pkgs.stdenv.hostPlatform.isx86_64 || pkgs.stdenv.hostPlatform.isDarwin;
ndkVersions = [
"23.1.7779620"
"25.1.8937393"
"26.1.10909125"
"latest"
];
androidComposition = androidEnv.composeAndroidPackages {
includeSources = true;
includeSystemImages = false;
includeEmulator = "if-supported";
includeNDK = "if-supported";
inherit ndkVersions;
useGoogleAPIs = true;
useGoogleTVAddOns = true;
# Make sure everything from the last decade works since we are not using system images.
numLatestPlatformVersions = 10;
# If you want to use a custom repo JSON:
# repoJson = ../repo.json;
# If you want to use custom repo XMLs:
/*
repoXmls = {
packages = [ ../xml/repository2-1.xml ];
images = [
../xml/android-sys-img2-1.xml
../xml/android-tv-sys-img2-1.xml
../xml/android-wear-sys-img2-1.xml
../xml/android-wear-cn-sys-img2-1.xml
../xml/google_apis-sys-img2-1.xml
../xml/google_apis_playstore-sys-img2-1.xml
];
addons = [ ../xml/addon2-1.xml ];
};
*/
includeExtras = [
"extras;google;gcm"
]
++ pkgs.lib.optionals includeAuto [
"extras;google;auto"
];
# Accepting more licenses declaratively:
extraLicenses = [
# Already accepted for you with the global accept_license = true or
# licenseAccepted = true on androidenv.
# "android-sdk-license"
# These aren't, but are useful for more uncommon setups.
"android-sdk-preview-license"
"android-googletv-license"
"android-sdk-arm-dbt-license"
"google-gdk-license"
"intel-android-extra-license"
"intel-android-sysimage-license"
"mips-android-sysimage-license"
];
};
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
firstSdk = pkgs.lib.foldl' pkgs.lib.min 100 androidComposition.platformVersions;
latestSdk = pkgs.lib.foldl' pkgs.lib.max 0 androidComposition.platformVersions;
jdk = pkgs.jdk;
in
pkgs.mkShell rec {
name = "androidenv-demo";
packages = [
androidSdk
platformTools
jdk
];
LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = jdk.home;
# Note: ANDROID_HOME is deprecated. Use ANDROID_SDK_ROOT.
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";
shellHook = ''
# Ensures that we don't have to use a FHS env by using the nix store's aapt2.
export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=$(echo "$ANDROID_SDK_ROOT/build-tools/"*"/aapt2")"
# Add cmake to the path.
cmake_root="$(echo "$ANDROID_SDK_ROOT/cmake/"*/)"
export PATH="$cmake_root/bin:$PATH"
# Write out local.properties for Android Studio.
cat <<EOF > local.properties
# This file was automatically generated by nix-shell.
sdk.dir=$ANDROID_SDK_ROOT
ndk.dir=$ANDROID_NDK_ROOT
cmake.dir=$cmake_root
EOF
'';
passthru.tests = {
shell-sdkmanager-licenses-test =
pkgs.runCommand "shell-sdkmanager-licenses-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
if [[ ! "$(sdkmanager --licenses)" =~ "All SDK package licenses accepted." ]]; then
echo "At least one of SDK package licenses are not accepted."
exit 1
fi
touch $out
'';
shell-sdkmanager-packages-test =
pkgs.runCommand "shell-sdkmanager-packages-test"
{
nativeBuildInputs = [
androidSdk
jdk
];
}
''
output="$(sdkmanager --list)"
installed_packages_section=$(echo "''${output%%Available Packages*}" | awk 'NR>4 {print $1}')
packages=(
"build-tools" "platform-tools" \
"extras;google;gcm"
)
for x in $(seq ${toString firstSdk} ${toString latestSdk}); do
if [ $x -ne 34 ]; then
# FIXME couldn't find platforms;android-34, even though it's in the correct directory!! sdkmanager's bug?!
packages+=("platforms;android-$x")
fi
packages+=("sources;android-$x")
done
${pkgs.lib.optionalString includeAuto ''packages+=("extras;google;auto")''}
for package in "''${packages[@]}"; do
if [[ ! $installed_packages_section =~ "$package" ]]; then
echo "$package package was not installed."
exit 1
fi
done
num_ndk_packages="$(echo "$installed_packages_section" | grep '^ndk;' | wc -l)"
if [ $num_ndk_packages -ne ${toString (pkgs.lib.length ndkVersions)} ]; then
echo "Invalid NDK package count: $num_ndk_packages"
exit 1
fi
touch "$out"
'';
};
}

View File

@@ -0,0 +1,43 @@
{
deployAndroidPackage,
lib,
package,
os,
arch,
autoPatchelfHook,
makeWrapper,
pkgs,
meta,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = [ makeWrapper ] ++ lib.optionals (os == "linux") [ autoPatchelfHook ];
buildInputs = lib.optionals (os == "linux") (
with pkgs;
[
llvmPackages.libcxx
SDL2
]
);
patchInstructions = lib.optionalString (os == "linux") ''
autoPatchelf --no-recurse $packageBaseDir
if [ -f $PWD/desktop-head-unit ]; then
echo "desktop-head-unit exists"
wrapProgram $PWD/desktop-head-unit \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath (
with pkgs;
[
xorg.libX11
xorg.libXrandr
]
)
}
fi
'';
inherit meta;
}

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl
die() {
echo "$1" >&2
exit 1
}
fetch() {
local url="https://dl.google.com/android/repository/$1"
echo "$url -> $2" >&2
curl -s "$url" -o "$2" || die "Failed to fetch $url"
}
pushd "$(dirname "$0")" &>/dev/null || exit 1
mkdir -p xml
fetch repository2-3.xml xml/repository2-3.xml
for img in android android-tv android-wear android-wear-cn android-automotive google_apis google_apis_playstore
do
fetch sys-img/$img/sys-img2-3.xml xml/$img-sys-img2-3.xml
done
fetch addon2-3.xml xml/addon2-3.xml
popd &>/dev/null

View File

@@ -0,0 +1,9 @@
{
config,
}:
# Accept the license in the test suite.
config.android_sdk.accept_license or (
builtins.getEnv "NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE" == "1"
|| builtins.getEnv "UPDATE_NIX_ATTR_PATH" == "androidenv.test-suite"
)

View File

@@ -0,0 +1,109 @@
{
stdenv,
lib,
pkgs,
pkgsHostHost,
makeWrapper,
autoPatchelfHook,
deployAndroidPackage,
package,
os,
arch,
platform-tools,
meta,
}:
let
runtime_paths =
lib.makeBinPath (
with pkgsHostHost;
[
coreutils
file
findutils
gawk
gnugrep
gnused
jdk
python3
which
]
)
+ ":${platform-tools}/platform-tools";
in
deployAndroidPackage rec {
inherit package os arch;
nativeBuildInputs = [
makeWrapper
]
++ lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ];
autoPatchelfIgnoreMissingDeps = [ "*" ];
buildInputs = lib.optionals (os == "linux") [
pkgs.zlib
pkgs.libcxx
(lib.getLib stdenv.cc.cc)
];
patchElfBnaries = ''
# Patch the executables of the toolchains, but not the libraries -- they are needed for crosscompiling
if [ -d $out/libexec/android-sdk/ndk-bundle/toolchains/renderscript/prebuilt/linux-x86_64/lib64 ]; then
addAutoPatchelfSearchPath $out/libexec/android-sdk/ndk-bundle/toolchains/renderscript/prebuilt/linux-x86_64/lib64
fi
if [ -d $out/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/lib64 ]; then
addAutoPatchelfSearchPath $out/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/lib64
fi
if [ -d $out/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/lib ]; then
addAutoPatchelfSearchPath $out/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/lib
fi
find toolchains -type d -name bin -or -name lib64 -or -name lib | while read dir; do
autoPatchelf "$dir"
done
# Patch executables
if [ -d prebuilt/linux-x86_64 ]; then
autoPatchelf prebuilt/linux-x86_64
fi
'';
patchOsAgnostic = ''
patchShebangs .
# TODO: allow this stuff
rm -rf docs tests
# Ndk now has a prebuilt toolchains inside, the file layout has changed, we do a symlink
# to still support the old standalone toolchains builds.
if [ -d $out/libexec/android-sdk/ndk ] && [ ! -d $out/libexec/android-sdk/ndk-bundle ]; then
ln -sf $out/libexec/android-sdk/ndk/${package.revision} $out/libexec/android-sdk/ndk-bundle
elif [ ! -d $out/libexec/android-sdk/ndk-bundle ]; then
echo "The ndk-bundle layout has changed. The nix expressions have to be updated!"
exit 1
fi
# fix ineffective PROGDIR / MYNDKDIR determination
for progname in ndk-build; do
sed -i -e 's|^PROGDIR=`dirname $0`|PROGDIR=`dirname $(readlink -f $(which $0))`|' $progname
done
# wrap
for progname in ndk-build; do
wrapProgram "$(pwd)/$progname" --prefix PATH : "${runtime_paths}"
done
# make some executables available in PATH
mkdir -p $out/bin
for progname in ndk-build; do
ln -sf ../libexec/android-sdk/ndk-bundle/$progname $out/bin/$progname
done
'';
patchInstructions =
patchOsAgnostic + lib.optionalString stdenv.hostPlatform.isLinux patchElfBnaries;
noAuditTmpdir = true; # Audit script gets invoked by the build/ component in the path for the make standalone script
inherit meta;
}

View File

@@ -0,0 +1,17 @@
{
deployAndroidPackage,
lib,
package,
os,
arch,
autoPatchelfHook,
stdenv,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ];
patchInstructions = lib.optionalString (os == "linux") ''
autoPatchelf $packageBaseDir/bin
'';
}

View File

@@ -0,0 +1,37 @@
{
deployAndroidPackage,
lib,
package,
os,
arch,
autoPatchelfHook,
pkgs,
meta,
}:
deployAndroidPackage {
inherit package os arch;
nativeBuildInputs = lib.optionals (os == "linux") [ autoPatchelfHook ];
buildInputs = lib.optionals (os == "linux") [
pkgs.glibc
(lib.getLib pkgs.stdenv.cc.cc)
pkgs.zlib
pkgs.ncurses5
];
patchInstructions =
lib.optionalString (os == "linux") ''
addAutoPatchelfSearchPath $packageBaseDir/lib64
autoPatchelf --no-recurse $packageBaseDir/lib64
autoPatchelf --no-recurse $packageBaseDir
''
+ ''
mkdir -p $out/bin
cd $out/bin
find $out/libexec/android-sdk/platform-tools -type f -executable -mindepth 1 -maxdepth 1 -not -name sqlite3 | while read i
do
ln -s $i
done
'';
inherit meta;
}

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p jq
set -e
pushd "$(dirname "$0")" &>/dev/null || exit 1
if [ "$1" == '' ]; then
echo "Please select a group: 'packages', 'images', 'addons', 'extras', or 'licenses'" >&2
exit 1
fi
namespace="$1"
if [ "$namespace" == 'licenses' ]; then
jq -r '.licenses | keys | join("\n")' < repo.json
else
jq -r --arg NAMESPACE "$namespace" \
'.[$NAMESPACE] | paths as $path | getpath($path) as $v | select($path[-1] == "displayName") | [[$NAMESPACE] + $path[:-1] | map("\"" + . + "\"") | join("."), $v] | join(": ")' \
< repo.json
fi
popd &>/dev/null

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
{
callPackage,
lib,
stdenv,
meta,
}:
let
examples-shell = callPackage ./examples/shell.nix { licenseAccepted = true; };
examples-shell-with-emulator = callPackage ./examples/shell-with-emulator.nix {
licenseAccepted = true;
};
examples-shell-without-emulator = callPackage ./examples/shell-without-emulator.nix {
licenseAccepted = true;
};
all-tests =
examples-shell.passthru.tests
// (examples-shell-with-emulator.passthru.tests // examples-shell-without-emulator.passthru.tests);
in
stdenv.mkDerivation {
name = "androidenv-test-suite";
version = lib.substring 0 8 (builtins.hashFile "sha256" ./repo.json);
buildInputs = lib.mapAttrsToList (name: value: value) all-tests;
buildCommand = ''
touch $out
'';
passthru.tests = all-tests;
passthru.updateScript = {
command = [ ./update.rb ];
attrPath = "androidenv.test-suite";
supportedFeatures = [ "commit" ];
};
meta = meta // {
timeout = 60;
};
}

View File

@@ -0,0 +1,83 @@
{
deployAndroidPackage,
lib,
stdenv,
package,
os,
arch,
autoPatchelfHook,
makeWrapper,
pkgs,
pkgsi686Linux,
postInstall,
meta,
}:
deployAndroidPackage {
name = "androidsdk-tools";
inherit package os arch;
nativeBuildInputs = [ makeWrapper ] ++ lib.optionals (os == "linux") [ autoPatchelfHook ];
buildInputs = lib.optional (os == "linux") (
(with pkgs; [
glibc
freetype
fontconfig
fontconfig.lib
stdenv.cc.cc.libgcc or null # fix for https://github.com/NixOS/nixpkgs/issues/226357
])
++ (with pkgs.xorg; [
libX11
libXrender
libXext
])
++ lib.optionals (os == "linux" && stdenv.isx86_64) (
with pkgsi686Linux;
[
glibc
xorg.libX11
xorg.libXrender
xorg.libXext
fontconfig.lib
freetype
zlib
]
)
);
patchInstructions = ''
${lib.optionalString (os == "linux") ''
# Auto patch all binaries
autoPatchelf .
''}
# Wrap all scripts that require JAVA_HOME
for i in bin; do
find $i -maxdepth 1 -type f -executable | while read program; do
if grep -q "JAVA_HOME" $program; then
wrapProgram $PWD/$program --prefix PATH : ${pkgs.jdk8}/bin
fi
done
done
# Wrap monitor script
wrapProgram $PWD/monitor \
--prefix PATH : ${pkgs.jdk8}/bin \
--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath (
with pkgs;
[
xorg.libX11
xorg.libXtst
]
)
}
# Patch all script shebangs
patchShebangs .
cd $out/libexec/android-sdk
${postInstall}
'';
inherit meta;
}

View File

@@ -0,0 +1,627 @@
#!/usr/bin/env nix-shell
#!nix-shell -i ruby -p "ruby.withPackages (ps: with ps; [ slop curb nokogiri ])"
require 'json'
require 'rubygems'
require 'shellwords'
require 'erb'
require 'uri'
require 'stringio'
require 'slop'
require 'curb'
require 'nokogiri'
# Returns a repo URL for a given package name.
def repo_url value
if value && value.start_with?('http')
value
elsif value
"https://dl.google.com/android/repository/#{value}"
else
nil
end
end
# Returns a system image URL for a given system image name.
def image_url value, dir
if dir == "default"
dir = "android"
end
if value && value.start_with?('http')
value
elsif value
"https://dl.google.com/android/repository/sys-img/#{dir}/#{value}"
else
nil
end
end
# Runs a GET with curl.
def _curl_get url
curl = Curl::Easy.new(url) do |http|
http.headers['User-Agent'] = 'nixpkgs androidenv update bot'
yield http if block_given?
end
STDERR.print "GET #{url}"
curl.perform
STDERR.puts "... #{curl.response_code}"
StringIO.new(curl.body_str)
end
# Retrieves a repo from the filesystem or a URL.
def get location
uri = URI.parse(location)
case uri.scheme
when 'repo'
_curl_get repo_url("#{uri.host}#{uri.fragment}.xml")
when 'image'
_curl_get image_url("sys-img#{uri.fragment}.xml", uri.host)
else
if File.exist?(uri.path)
File.open(uri.path, 'rt')
else
raise "Repository #{uri} was neither a file nor a repo URL"
end
end
end
# Returns a JSON with the data and structure of the input XML
def to_json_collector doc
json = {}
index = 0
doc.element_children.each { |node|
if node.children.length == 1 and node.children.first.text?
json["#{node.name}:#{index}"] ||= node.content
index += 1
next
end
json["#{node.name}:#{index}"] ||= to_json_collector node
index += 1
}
element_attributes = {}
doc.attribute_nodes.each do |attr|
if attr.name == "type"
type = attr.value.split(':', 2).last
case attr.value
when 'generic:genericDetailsType'
element_attributes["xsi:type"] ||= "ns5:#{type}"
when 'addon:extraDetailsType'
element_attributes["xsi:type"] ||= "ns8:#{type}"
when 'addon:mavenType'
element_attributes["xsi:type"] ||= "ns8:#{type}"
when 'sdk:platformDetailsType'
element_attributes["xsi:type"] ||= "ns11:#{type}"
when 'sdk:sourceDetailsType'
element_attributes["xsi:type"] ||= "ns11:#{type}"
when 'sys-img:sysImgDetailsType'
element_attributes["xsi:type"] ||= "ns12:#{type}"
when 'addon:addonDetailsType' then
element_attributes["xsi:type"] ||= "ns8:#{type}"
end
else
element_attributes[attr.name] ||= attr.value
end
end
if !element_attributes.empty?
json['element-attributes'] ||= element_attributes
end
json
end
# Returns a tuple of [type, revision, revision components] for a package node.
def package_revision package
type_details = package.at_css('> type-details')
type = type_details.attributes['type']
type &&= type.value
revision = nil
components = nil
case type
when 'generic:genericDetailsType', 'addon:extraDetailsType', 'addon:mavenType'
major = text package.at_css('> revision > major')
minor = text package.at_css('> revision > minor')
micro = text package.at_css('> revision > micro')
preview = text package.at_css('> revision > preview')
revision = ''
components = []
unless empty?(major)
revision << major
components << major
end
unless empty?(minor)
revision << ".#{minor}"
components << minor
end
unless empty?(micro)
revision << ".#{micro}"
components << micro
end
unless empty?(preview)
revision << "-rc#{preview}"
components << preview
end
when 'sdk:platformDetailsType'
codename = text type_details.at_css('> codename')
api_level = text type_details.at_css('> api-level')
revision = empty?(codename) ? api_level : codename
components = [revision]
when 'sdk:sourceDetailsType'
api_level = text type_details.at_css('> api-level')
revision, components = api_level, [api_level]
when 'sys-img:sysImgDetailsType'
codename = text type_details.at_css('> codename')
api_level = text type_details.at_css('> api-level')
id = text type_details.at_css('> tag > id')
abi = text type_details.at_css('> abi')
revision = ''
components = []
if empty?(codename)
revision << api_level
components << api_level
else
revision << codename
components << codename
end
unless empty?(id)
revision << "-#{id}"
components << id
end
unless empty?(abi)
revision << "-#{abi}"
components << abi
end
when 'addon:addonDetailsType' then
api_level = text type_details.at_css('> api-level')
id = text type_details.at_css('> tag > id')
revision = api_level
components = [api_level, id]
end
[type, revision, components]
end
# Returns a hash of archives for the specified package node.
def package_archives package
archives = {}
package.css('> archives > archive').each do |archive|
host_os = text archive.at_css('> host-os')
host_arch = text archive.at_css('> host-arch')
host_os = 'all' if empty?(host_os)
host_arch = 'all' if empty?(host_arch)
archives[host_os + host_arch] = {
'os' => host_os,
'arch' => host_arch,
'size' => Integer(text(archive.at_css('> complete > size'))),
'sha1' => text(archive.at_css('> complete > checksum')),
'url' => yield(text(archive.at_css('> complete > url')))
}
end
archives
end
# Returns the text from a node, or nil.
def text node
node ? node.text : nil
end
# Nil or empty helper.
def empty? value
!value || value.empty?
end
# Fixes up returned hashes by converting archives like
# (e.g. {'linux' => {'sha1' => ...}, 'macosx' => ...} to
# [{'os' => 'linux', 'sha1' => ...}, {'os' => 'macosx', ...}, ...].
def fixup value
Hash[value.map do |k, v|
if k == 'archives' && v.is_a?(Hash)
[k, v.map do |os, archive|
fixup(archive)
end]
elsif v.is_a?(Hash)
[k, fixup(v)]
else
[k, v]
end
end]
end
# Today since Unix Epoch, January 1, 1970.
def today
Time.now.utc.to_i / 24 / 60 / 60
end
# The expiration strategy. Expire if the last available day was before the `oldest_valid_day`.
def expire_records record, oldest_valid_day
if record.is_a?(Hash)
if record.has_key?('last-available-day') &&
record['last-available-day'] < oldest_valid_day
return nil
end
update = {}
# This should only happen in the first run of this scrip after adding the `expire_record` function.
if record.has_key?('displayName') &&
!record.has_key?('last-available-day')
update['last-available-day'] = today
end
record.each {|key, value|
v = expire_records value, oldest_valid_day
update[key] = v if v
}
update
else
record
end
end
# Normalize the specified license text.
# See: https://brash-snapper.glitch.me/ for how the munging works.
def normalize_license license
license = license.dup
license.gsub!(/([^\n])\n([^\n])/m, '\1 \2')
license.gsub!(/ +/, ' ')
license.strip!
license
end
# Gets all license texts, deduplicating them.
def get_licenses doc
licenses = {}
doc.css('license[type="text"]').each do |license_node|
license_id = license_node['id']
if license_id
licenses[license_id] ||= []
licenses[license_id] |= [normalize_license(text(license_node))]
end
end
licenses
end
def parse_package_xml doc
licenses = get_licenses doc
packages = {}
# check https://github.com/NixOS/nixpkgs/issues/373785
extras = {}
doc.css('remotePackage').each do |package|
name, _, version = package['path'].partition(';')
next if version == 'latest'
is_extras = name == 'extras'
if is_extras
name = package['path'].tr(';', '-')
end
type, revision, _ = package_revision(package)
next unless revision
path = package['path'].tr(';', '/')
display_name = text package.at_css('> display-name')
uses_license = package.at_css('> uses-license')
uses_license &&= uses_license['ref']
obsolete ||= package['obsolete']
type_details = to_json_collector package.at_css('> type-details')
revision_details = to_json_collector package.at_css('> revision')
archives = package_archives(package) {|url| repo_url url}
dependencies_xml = package.at_css('> dependencies')
dependencies = to_json_collector dependencies_xml if dependencies_xml
if is_extras
target = extras
component = package['path']
target = (target[component] ||= {})
else
target = (packages[name] ||= {})
target = (target[revision] ||= {})
end
target['name'] ||= name
target['path'] ||= path
target['revision'] ||= revision
target['displayName'] ||= display_name
target['license'] ||= uses_license if uses_license
target['obsolete'] ||= obsolete if obsolete == 'true'
target['type-details'] ||= type_details
target['revision-details'] ||= revision_details
target['dependencies'] ||= dependencies if dependencies
target['archives'] ||= {}
merge target['archives'], archives
target['last-available-day'] = today
end
[licenses, packages, extras]
end
def parse_image_xml doc
licenses = get_licenses doc
images = {}
doc.css('remotePackage[path^="system-images;"]').each do |package|
type, revision, components = package_revision(package)
next unless revision
path = package['path'].tr(';', '/')
display_name = text package.at_css('> display-name')
uses_license = package.at_css('> uses-license')
uses_license &&= uses_license['ref']
obsolete &&= package['obsolete']
type_details = to_json_collector package.at_css('> type-details')
revision_details = to_json_collector package.at_css('> revision')
archives = package_archives(package) {|url| image_url url, components[-2]}
dependencies_xml = package.at_css('> dependencies')
dependencies = to_json_collector dependencies_xml if dependencies_xml
target = images
components.each do |component|
target[component] ||= {}
target = target[component]
end
target['name'] ||= "system-image-#{revision}"
target['path'] ||= path
target['revision'] ||= revision
target['displayName'] ||= display_name
target['license'] ||= uses_license if uses_license
target['obsolete'] ||= obsolete if obsolete
target['type-details'] ||= type_details
target['revision-details'] ||= revision_details
target['dependencies'] ||= dependencies if dependencies
target['archives'] ||= {}
merge target['archives'], archives
target['last-available-day'] = today
end
[licenses, images]
end
def parse_addon_xml doc
licenses = get_licenses doc
addons, extras = {}, {}
doc.css('remotePackage').each do |package|
type, revision, components = package_revision(package)
next unless revision
path = package['path'].tr(';', '/')
display_name = text package.at_css('> display-name')
uses_license = package.at_css('> uses-license')
uses_license &&= uses_license['ref']
obsolete &&= package['obsolete']
type_details = to_json_collector package.at_css('> type-details')
revision_details = to_json_collector package.at_css('> revision')
archives = package_archives(package) {|url| repo_url url}
dependencies_xml = package.at_css('> dependencies')
dependencies = to_json_collector dependencies_xml if dependencies_xml
case type
when 'addon:addonDetailsType'
name = components.last
target = addons
# Hack for Google APIs 25 r1, which displays as 23 for some reason
archive_name = text package.at_css('> archives > archive > complete > url')
if archive_name == 'google_apis-25_r1.zip'
path = 'add-ons/addon-google_apis-google-25'
revision = '25'
components = [revision, components.last]
end
when 'addon:extraDetailsType', 'addon:mavenType'
name = package['path'].tr(';', '-')
components = [package['path']]
target = extras
end
components.each do |component|
target = (target[component] ||= {})
end
target['name'] ||= name
target['path'] ||= path
target['revision'] ||= revision
target['displayName'] ||= display_name
target['license'] ||= uses_license if uses_license
target['obsolete'] ||= obsolete if obsolete
target['type-details'] ||= type_details
target['revision-details'] ||= revision_details
target['dependencies'] ||= dependencies if dependencies
target['archives'] ||= {}
merge target['archives'], archives
target['last-available-day'] = today
end
[licenses, addons, extras]
end
# Make the clean diff by always sorting the result before puting it in the stdout.
def sort_recursively value
if value.is_a?(Hash)
Hash[
value.map do |k, v|
[k, sort_recursively(v)]
end.sort_by {|(k, v)| k }
]
elsif value.is_a?(Array)
value.map do |v| sort_recursively(v) end
else
value
end
end
def merge_recursively a, b
a.merge!(b) {|key, a_item, b_item|
if a_item.is_a?(Hash) && b_item.is_a?(Hash)
merge_recursively(a_item, b_item)
elsif b_item != nil
b_item
end
}
a
end
def merge dest, src
merge_recursively dest, src
end
opts = Slop.parse do |o|
o.array '-p', '--packages', 'packages repo XMLs to parse', default: %w[repo://repository#2-3]
o.array '-i', '--images', 'system image repo XMLs to parse', default: %w[
image://android#2-3
image://android-tv#2-3
image://android-wear#2-3
image://android-wear-cn#2-3
image://android-automotive#2-3
image://google_apis#2-3
image://google_apis_playstore#2-3
]
o.array '-a', '--addons', 'addon repo XMLs to parse', default: %w[repo://addon#2-3]
o.string '-I', '--input', 'input JSON file for repo', default: File.join(__dir__, 'repo.json')
o.string '-O', '--output', 'output JSON file for repo', default: File.join(__dir__, 'repo.json')
end
result = {}
result['licenses'] = {}
result['packages'] = {}
result['images'] = {}
result['addons'] = {}
result['extras'] = {}
opts[:packages].each do |filename|
licenses, packages, extras = parse_package_xml(Nokogiri::XML(get(filename)) { |conf| conf.noblanks })
merge result['licenses'], licenses
merge result['packages'], packages
merge result['extras'], extras
end
opts[:images].each do |filename|
licenses, images = parse_image_xml(Nokogiri::XML(get(filename)) { |conf| conf.noblanks })
merge result['licenses'], licenses
merge result['images'], images
end
opts[:addons].each do |filename|
licenses, addons, extras = parse_addon_xml(Nokogiri::XML(get(filename)) { |conf| conf.noblanks })
merge result['licenses'], licenses
merge result['addons'], addons
merge result['extras'], extras
end
result['latest'] = {}
result['packages'].each do |name, versions|
max_version = Gem::Version.new('0')
versions.each do |version, package|
if package['license'] == 'android-sdk-license' && Gem::Version.correct?(package['revision'])
package_version = Gem::Version.new(package['revision'])
max_version = package_version if package_version > max_version
end
end
result['latest'][name] = max_version.to_s
end
# As we keep the old packages in the repo JSON file, we should have
# a strategy to remove them at some point!
# So with this variable we claim it's okay to remove them from the
# JSON after two years that they are not available.
two_years_ago = today - 2 * 365
input = {}
prev_latest = {}
begin
input_json = if File.exist?(opts[:input])
STDERR.puts "Reading #{opts[:input]}"
File.read(opts[:input])
else
STDERR.puts "Creating new repo"
"{}"
end
if input_json != nil && !input_json.empty?
input = expire_records(JSON.parse(input_json), two_years_ago)
# Just create a new set of latest packages.
prev_latest = input['latest'] || {}
input['latest'] = {}
end
rescue JSON::ParserError => e
STDERR.write(e.message)
return
end
fixup_result = fixup(result)
# Regular installation of Android SDK would keep the previously installed packages even if they are not
# in the uptodate XML files, so here we try to support this logic by keeping un-available packages,
# therefore the old packages will work as long as the links are working on the Google servers.
output = merge input, fixup_result
# Write the repository. Append a \n to keep nixpkgs Github Actions happy.
STDERR.puts "Writing #{opts[:output]}"
File.write opts[:output], (JSON.pretty_generate(sort_recursively(output)) + "\n")
# Output metadata for the nixpkgs update script.
if ENV['UPDATE_NIX_ATTR_PATH']
# See if there are any changes in the latest versions.
cur_latest = output['latest'] || {}
old_versions = []
new_versions = []
changes = []
changed = false
cur_latest.each do |k, v|
prev = prev_latest[k]
if prev && prev != v
old_versions << "#{k}:#{prev}"
new_versions << "#{k}:#{v}"
changes << "#{k}: #{prev} -> #{v}"
changed = true
end
end
changed_paths = []
if changed
# Instantiate it.
test_result = `NIXPKGS_ALLOW_UNFREE=1 NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1 nix-build #{Shellwords.escape(File.realpath(File.join(__dir__, '..', '..', '..', '..', 'default.nix')))} -A #{Shellwords.join [ENV['UPDATE_NIX_ATTR_PATH']]} 2>&1`
test_status = $?.exitstatus
template = ERB.new(<<-EOF, trim_mode: '<>-')
androidenv: <%= changes.join('; ') %>
Performed the following automatic androidenv updates:
<% changes.each do |change| %>
- <%= change -%>
<% end %>
Tests exited with status: <%= test_status -%>
<% if !test_result.empty? %>
Last 100 lines of output:
```
<%= test_result.lines.last(100).join -%>
```
<% end %>
EOF
changed_paths << {
attrPath: 'androidenv.androidPkgs.androidsdk',
oldVersion: old_versions.join('; '),
newVersion: new_versions.join('; '),
files: [
opts[:output]
],
commitMessage: template.result(binding)
}
end
# nix-update info is on stdout
STDOUT.puts JSON.pretty_generate(changed_paths)
end

View File

@@ -0,0 +1,37 @@
{
lib,
stdenv,
fetchFromGitHub,
cmake,
}:
stdenv.mkDerivation rec {
pname = "cmake-modules-webos";
version = "19";
src = fetchFromGitHub {
owner = "openwebos";
repo = "cmake-modules-webos";
rev = "submissions/${version}";
sha256 = "1l4hpcmgc98kp9g1642sy111ki5qyk3q7j10xzkgmnvz8lqffnxp";
};
nativeBuildInputs = [ cmake ];
prePatch = ''
substituteInPlace CMakeLists.txt --replace "CMAKE_ROOT}/Modules" "CMAKE_INSTALL_PREFIX}/lib/cmake"
substituteInPlace webOS/webOS.cmake \
--replace ' ''${CMAKE_ROOT}/Modules' " $out/lib/cmake" \
--replace 'INSTALL_ROOT}/usr' 'INSTALL_ROOT}'
sed -i '/CMAKE_INSTALL_PREFIX/d' webOS/webOS.cmake
'';
setupHook = ./cmake-setup-hook.sh;
meta = with lib; {
description = "CMake modules needed to build Open WebOS components";
license = licenses.asl20;
maintainers = [ ];
};
}

View File

@@ -0,0 +1,9 @@
_addWebOSCMakeFlags() {
# Help find the webOS cmake module
cmakeFlagsArray+=(-DCMAKE_MODULE_PATH=@out@/lib/cmake)
# fix installation path (doesn't use CMAKE_INSTALL_PREFIX)
cmakeFlagsArray+=(-DWEBOS_INSTALL_ROOT=${!outputBin})
}
preConfigureHooks+=(_addWebOSCMakeFlags)

View File

@@ -0,0 +1,38 @@
{
lib,
stdenv,
fetchFromGitHub,
webos,
cmake,
pkg-config,
}:
stdenv.mkDerivation rec {
pname = "novacom";
version = "18";
src = fetchFromGitHub {
owner = "openwebos";
repo = "novacom";
rev = "submissions/${version}";
sha256 = "12s6g7l20kakyjlhqpli496miv2kfsdp17lcwhdrzdxvxl6hnf4n";
};
nativeBuildInputs = [
cmake
pkg-config
webos.cmake-modules
];
postInstall = ''
install -Dm755 -t $out/bin ../scripts/novaterm
substituteInPlace $out/bin/novaterm --replace "exec novacom" "exec $out/bin/novacom"
'';
meta = with lib; {
description = "Utility for communicating with WebOS devices";
license = licenses.asl20;
maintainers = [ ];
platforms = platforms.linux;
};
}

View File

@@ -0,0 +1,59 @@
{
lib,
stdenv,
fetchFromGitHub,
fetchpatch,
webos,
cmake,
pkg-config,
nixosTests,
libusb-compat-0_1,
}:
stdenv.mkDerivation rec {
pname = "novacomd";
version = "127";
src = fetchFromGitHub {
owner = "openwebos";
repo = "novacomd";
rev = "submissions/${version}";
sha256 = "1gahc8bvvvs4d6svrsw24iw5r0mhy4a2ars3j2gz6mp6sh42bznl";
};
patches = [
(fetchpatch {
url = "https://aur.archlinux.org/cgit/aur.git/plain/0001-Use-usb_bulk_-read-write-instead-of-homemade-handler.patch?h=palm-novacom-git";
sha256 = "116r6p4l767fqxfvq03sy6v7vxja8pkxlrc5hqby351a40b5dkiv";
})
(fetchpatch {
url = "https://raw.githubusercontent.com/feniksa/webos-overlay/40e2c113fc9426d50bdf37779da57ce4ff06ee6e/net-misc/novacomd/files/0011-Remove-verbose-output.patch";
sha256 = "09lmv06ziwkfg19b1h3jsmkm6g1f0nxxq1717dircjx8m45ypjq9";
})
];
nativeBuildInputs = [
cmake
pkg-config
webos.cmake-modules
];
buildInputs = [ libusb-compat-0_1 ];
# Workaround build failure on -fno-common toolchains:
# ld: src/host/usb-linux.c:82: multiple definition of `t_recovery_queue';
# src/host/recovery.c:45: first defined here
env.NIX_CFLAGS_COMPILE = "-fcommon";
cmakeFlags = [ "-DWEBOS_TARGET_MACHINE_IMPL=host" ];
passthru.tests = { inherit (nixosTests) novacomd; };
meta = with lib; {
description = "Daemon for communicating with WebOS devices";
mainProgram = "novacomd";
license = licenses.asl20;
maintainers = [ ];
platforms = platforms.linux;
};
}

View File

@@ -0,0 +1,186 @@
{
stdenv,
lib,
composeXcodeWrapper,
}:
{
name,
src,
sdkVersion ? "13.1",
target ? null,
configuration ? null,
scheme ? null,
sdk ? null,
xcodeFlags ? "",
release ? false,
certificateFile ? null,
certificatePassword ? null,
provisioningProfile ? null,
codeSignIdentity ? null,
signMethod ? null,
generateIPA ? false,
generateXCArchive ? false,
enableWirelessDistribution ? false,
installURL ? null,
bundleId ? null,
appVersion ? null,
...
}@args:
assert
release
->
certificateFile != null
&& certificatePassword != null
&& provisioningProfile != null
&& signMethod != null
&& codeSignIdentity != null;
assert enableWirelessDistribution -> installURL != null && bundleId != null && appVersion != null;
let
# Set some default values here
_target = if target == null then name else target;
_configuration =
if configuration == null then if release then "Release" else "Debug" else configuration;
_sdk =
if sdk == null then
if release then "iphoneos" + sdkVersion else "iphonesimulator" + sdkVersion
else
sdk;
# The following is to prevent repetition
deleteKeychain = ''
security default-keychain -s login.keychain
security delete-keychain $keychainName
'';
xcodewrapperFormalArgs = builtins.functionArgs composeXcodeWrapper;
xcodewrapperArgs = builtins.intersectAttrs xcodewrapperFormalArgs args;
xcodewrapper = composeXcodeWrapper xcodewrapperArgs;
extraArgs = removeAttrs args (
[
"name"
"scheme"
"xcodeFlags"
"release"
"certificateFile"
"certificatePassword"
"provisioningProfile"
"signMethod"
"generateIPA"
"generateXCArchive"
"enableWirelessDistribution"
"installURL"
"bundleId"
"version"
]
++ builtins.attrNames xcodewrapperFormalArgs
);
in
stdenv.mkDerivation (
{
name = lib.replaceStrings [ " " ] [ "" ] name; # iOS app names can contain spaces, but in the Nix store this is not allowed
buildPhase = ''
# Be sure that the Xcode wrapper has priority over everything else.
# When using buildInputs this does not seem to be the case.
export PATH=${xcodewrapper}/bin:$PATH
${lib.optionalString release ''
export HOME=/Users/$(whoami)
keychainName="$(basename $out)"
# Create a keychain
security create-keychain -p "" $keychainName
security default-keychain -s $keychainName
security unlock-keychain -p "" $keychainName
# Import the certificate into the keychain
security import ${certificateFile} -k $keychainName -P "${certificatePassword}" -A
# Grant the codesign utility permissions to read from the keychain
security set-key-partition-list -S apple-tool:,apple: -s -k "" $keychainName
# Determine provisioning ID
PROVISIONING_PROFILE=$(grep UUID -A1 -a ${provisioningProfile} | grep -o "[-A-Za-z0-9]\{36\}")
if [ ! -f "$HOME/Library/MobileDevice/Provisioning Profiles/$PROVISIONING_PROFILE.mobileprovision" ]
then
# Copy provisioning profile into the home directory
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
cp ${provisioningProfile} "$HOME/Library/MobileDevice/Provisioning Profiles/$PROVISIONING_PROFILE.mobileprovision"
fi
# Check whether the identity can be found
security find-identity -p codesigning $keychainName
''}
# Do the building
export LD=/usr/bin/clang # To avoid problem with -isysroot parameter that is unrecognized by the stock ld. Comparison with an impure build shows that it uses clang instead. Ugly, but it works
xcodebuild -target ${_target} -configuration ${_configuration} ${
lib.optionalString (scheme != null) "-scheme ${scheme}"
} -sdk ${_sdk} TARGETED_DEVICE_FAMILY="1, 2" ONLY_ACTIVE_ARCH=NO CONFIGURATION_TEMP_DIR=$TMPDIR CONFIGURATION_BUILD_DIR=$out ${
lib.optionalString (generateIPA || generateXCArchive) "-archivePath \"${name}.xcarchive\" archive"
} ${lib.optionalString release ''PROVISIONING_PROFILE=$PROVISIONING_PROFILE OTHER_CODE_SIGN_FLAGS="--keychain $HOME/Library/Keychains/$keychainName-db"''} ${xcodeFlags}
${lib.optionalString release ''
${lib.optionalString generateIPA ''
# Create export plist file
cat > "${name}.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>signingCertificate</key>
<string>${codeSignIdentity}</string>
<key>provisioningProfiles</key>
<dict>
<key>${bundleId}</key>
<string>$PROVISIONING_PROFILE</string>
</dict>
<key>signingStyle</key>
<string>manual</string>
<key>method</key>
<string>${signMethod}</string>
${lib.optionalString (signMethod == "enterprise" || signMethod == "ad-hoc") ''
<key>compileBitcode</key>
<false/>
''}
</dict>
</plist>
EOF
# Produce an IPA file
xcodebuild -exportArchive -archivePath "${name}.xcarchive" -exportOptionsPlist "${name}.plist" -exportPath $out
# Add IPA to Hydra build products
mkdir -p $out/nix-support
echo "file binary-dist \"$(echo $out/*.ipa)\"" > $out/nix-support/hydra-build-products
${lib.optionalString enableWirelessDistribution ''
# Add another hacky build product that enables wireless adhoc installations
appname="$(basename "$(echo $out/*.ipa)" .ipa)"
sed -e "s|@INSTALL_URL@|${installURL}?bundleId=${bundleId}\&amp;version=${appVersion}\&amp;title=$appname|" ${./install.html.template} > $out/''${appname}.html
echo "doc install \"$out/''${appname}.html\"" >> $out/nix-support/hydra-build-products
''}
''}
${lib.optionalString generateXCArchive ''
mkdir -p $out
mv "${name}.xcarchive" $out
''}
# Delete our temp keychain
${deleteKeychain}
''}
'';
failureHook = lib.optionalString release deleteKeychain;
installPhase = "true";
}
// extraArgs
)

View File

@@ -0,0 +1,62 @@
{
lib,
stdenv,
writeShellScriptBin,
}:
{
versions ? [ ],
xcodeBaseDir ? "/Applications/Xcode.app",
}:
assert stdenv.hostPlatform.isDarwin;
let
xcodebuildPath = "${xcodeBaseDir}/Contents/Developer/usr/bin/xcodebuild";
xcodebuildWrapper = writeShellScriptBin "xcodebuild" ''
currentVer="$(${xcodebuildPath} -version | awk 'NR==1{print $2}')"
wrapperVers=(${lib.concatStringsSep " " versions})
for ver in "''${wrapperVers[@]}"; do
if [[ "$currentVer" == "$ver" ]]; then
# here exec replaces the shell without creating a new process
# https://www.gnu.org/software/bash/manual/bash.html#index-exec
exec "${xcodebuildPath}" "$@"
fi
done
echo "The installed Xcode version ($currentVer) does not match any of the allowed versions: ${lib.concatStringsSep ", " versions}"
echo "Please update your local Xcode installation to match one of the allowed versions"
exit 1
'';
in
stdenv.mkDerivation {
name = "xcode-wrapper-impure";
# Fails in sandbox. Use `--option sandbox relaxed` or `--option sandbox false`.
__noChroot = true;
buildCommand = ''
mkdir -p $out/bin
cd $out/bin
${
if versions == [ ] then
''
ln -s "${xcodebuildPath}"
''
else
''
ln -s "${xcodebuildWrapper}/bin/xcode-select"
''
}
ln -s /usr/bin/security
ln -s /usr/bin/codesign
ln -s /usr/bin/xcrun
ln -s /usr/bin/plutil
ln -s /usr/bin/clang
ln -s /usr/bin/lipo
ln -s /usr/bin/file
ln -s /usr/bin/rev
ln -s "${xcodeBaseDir}/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator"
cd ..
ln -s "${xcodeBaseDir}/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs"
'';
}

View File

@@ -0,0 +1,9 @@
{ callPackage }:
rec {
composeXcodeWrapper = callPackage ./compose-xcodewrapper.nix { };
buildApp = callPackage ./build-app.nix { inherit composeXcodeWrapper; };
simulateApp = callPackage ./simulate-app.nix { inherit composeXcodeWrapper; };
}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Install IPA</title>
</head>
<body>
<a id="forwardlink" href="@INSTALL_URL@">Go to the install page or wait a second</a>
<script type="text/javascript">
setTimeout(function() {
var link = document.getElementById('forwardlink');
if(document.createEvent) {
var eventObj = document.createEvent('MouseEvents');
eventObj.initEvent('click', true, false);
link.dispatchEvent(eventObj);
} else if(document.createEventObject) {
link.fireEvent('onclick');
}
}, 1000);
</script>
</body>
</html>

View File

@@ -0,0 +1,65 @@
{
stdenv,
lib,
composeXcodeWrapper,
}:
{
name,
app ? null,
bundleId ? null,
...
}@args:
assert app != null -> bundleId != null;
let
xcodewrapperArgs = builtins.intersectAttrs (builtins.functionArgs composeXcodeWrapper) args;
xcodewrapper = composeXcodeWrapper xcodewrapperArgs;
in
stdenv.mkDerivation {
name = lib.replaceStrings [ " " ] [ "" ] name;
buildCommand = ''
mkdir -p $out/bin
cat > $out/bin/run-test-simulator << "EOF"
#! ${stdenv.shell} -e
if [ "$1" = "" ]
then
# Show the user the possibile UDIDs and let him pick one, if none is provided as a command-line parameter
xcrun simctl list
echo "Please provide a UDID of a simulator:"
read udid
else
# If a parameter has been provided, consider that a device UDID and use that
udid="$1"
fi
# Open the simulator instance
open -a "$(readlink "${xcodewrapper}/bin/Simulator")" --args -CurrentDeviceUDID $udid
${lib.optionalString (app != null) ''
# Copy the app and restore the write permissions
appTmpDir=$(mktemp -d -t appTmpDir)
cp -r "$(echo ${app}/*.app)" "$appTmpDir"
chmod -R 755 "$(echo $appTmpDir/*.app)"
# Wait for the simulator to start
echo "Press enter when the simulator is started..."
read
# Install the app
xcrun simctl install "$udid" "$(echo $appTmpDir/*.app)"
# Remove the app tempdir
rm -Rf $appTmpDir
# Launch the app in the simulator
xcrun simctl launch $udid "${bundleId}"
EOF
chmod +x $out/bin/run-test-simulator
''}
'';
}