{ description = "APKs demonstrating class shadowing."; inputs = { nixpkgs.url = github:nixos/nixpkgs/nixos-unstable; flake-utils.url = github:numtide/flake-utils; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { system = system; config = { android_sdk.accept_license = true; allowUnfree = true; }; }; jdk = pkgs.javaPackages.compiler.openjdk17; android-env = pkgs.androidenv.composeAndroidPackages { buildToolsVersions = [ "34.0.0" ]; platformVersions = [ "34" ]; }; platforms = "${builtins.head android-env.platforms}/libexec/android-sdk/platforms/android-34"; build-tools = "${builtins.head android-env.build-tools}/libexec/android-sdk/build-tools/34.0.0"; javaCompile = folder: pkgs.stdenvNoCC.mkDerivation { name = "java-class-${folder}"; src = self; buildInputs = [ jdk android-env.androidsdk ]; phases = ["unpackPhase" "buildPhase"]; buildPhase = '' mkdir $out javac -classpath ${platforms}/android.jar $(find ${folder} -type f -name '*.java') -d $out/ ''; }; java2dex = drv: pkgs.stdenvNoCC.mkDerivation { name = "${drv}-classes.dex"; srcs = drv; # jdk env needed for d8 buildInputs = [ jdk android-env.androidsdk ]; phases = ["unpackPhase" "buildPhase" "installPhase"]; buildPhase = '' mkdir build d8 --classpath ${platforms}/android.jar $(find . -name '*.class') --output build/ ''; installPhase = '' mv build/classes.dex $out ''; }; make-manifest = package : name: pkgs.stdenvNoCC.mkDerivation { name = "AndroidManifest-${package}.xml"; srcs = self; #buildInputs = [ ]; phases = ["unpackPhase" "buildPhase"]; buildPhase = '' mkdir $out cat AndroidManifest.xml | sed 's/PACKAGE/${package}/' | sed 's/NAME/${name}/' > $out/AndroidManifest.xml ''; }; keypass = "P@ssw0rd!"; keystore = pkgs.stdenvNoCC.mkDerivation { name = "shadowing.keystore"; buildInputs = [ jdk ]; phases = ["buildPhase"]; buildPhase = '' keytool -genkeypair -validity 1000 -dname "CN=SomeKey,O=SomeOne,C=FR" -keystore $out -storepass '${keypass}' -keypass '${keypass}' -alias SignKey -keyalg RSA -v ''; }; make-apk = { package, name, classes, } : let manifest = make-manifest package name; in pkgs.stdenvNoCC.mkDerivation { name = "${package}"; buildInputs = [ jdk android-env.androidsdk ]; phases = ["buildPhase" "installPhase"]; buildPhase = '' mkdir -p build/apk_files ${pkgs.lib.strings.concatMapStringsSep "\n" ( item: "cp ${item.drv} build/apk_files/${item.name}" ) classes} # link classes.dex not working? # ${build-tools}/aapt2 link -v ---manifest ${manifest}/AndroidManifest.xml -I ${platforms}/android.jar -o build/app.apk build/apk_files/ ${build-tools}/aapt package -v -f -M ${manifest}/AndroidManifest.xml -I ${platforms}/android.jar -F build/app.apk build/apk_files/ ${build-tools}/zipalign -f 4 build/app.apk build/app.aligned.apk ${build-tools}/apksigner sign -ks ${keystore} --v2-signing-enabled true --in build/app.aligned.apk --out build/${package}.apk --ks-pass 'pass:${keypass}' ''; installPhase = '' mkdir $out cp build/${package}.apk $out/ cp build/${package}.apk.idsig $out/ ''; }; java-classes-main = pkgs.stdenvNoCC.mkDerivation { name = "java-class-main"; src = self; buildInputs = [ jdk android-env.androidsdk ]; phases = ["unpackPhase" "buildPhase"]; buildPhase = '' mkdir $out javac -classpath ${platforms}/android.jar $(find java/main -type f -name '*.java') $(find java/actual -type f -name '*.java') -d $out/ find java/actual/ -type f -name '*.java' | while read f ; do class_file=$(echo $f | sed "s#java/actual/##" | sed 's/.java/.class/') rm $out/$class_file done ''; }; java-classes-actual = javaCompile "java/actual"; java-classes-dummy = javaCompile "java/dummy"; java-classes-empty = javaCompile "java/empty"; main-actual-classes-dex = java2dex (pkgs.symlinkJoin { name = "classes-main-actual"; paths = [ java-classes-main java-classes-actual ]; }); main-dummy-classes-dex = java2dex (pkgs.symlinkJoin { name = "classes-main-dummy"; paths = [ java-classes-main java-classes-dummy ]; }); main-classes-dex = java2dex java-classes-main; actual-classes-dex = java2dex java-classes-actual; dummy-classes-dex = java2dex java-classes-dummy; empty-classes-dex = java2dex java-classes-empty; in { packages = rec { control = make-apk { package = "com.example.shadowing.control"; name = "Shadowing C"; classes = [ { name = "classes.dex"; drv = main-actual-classes-dex; } ]; }; # classes.dex vs classes2.dex shdw1 = make-apk { package = "com.example.shadowing.one"; name = "Shadowing 1"; classes = [ { name = "classes.dex"; drv = main-actual-classes-dex; } { name = "classes2.dex"; drv = dummy-classes-dex; } ]; }; # classes2.dex vs classes3.dex shdw2 = make-apk { package = "com.example.shadowing.two"; name = "Shadowing 2"; classes = [ { name = "classes.dex"; drv = main-classes-dex; } { name = "classes2.dex"; drv = actual-classes-dex; } { name = "classes3.dex"; drv = dummy-classes-dex; } ]; }; # classes2.dex vs classes1.dex shdw3 = make-apk { package = "com.example.shadowing.three"; name = "Shadowing 3"; classes = [ { name = "classes.dex"; drv = main-classes-dex; } { name = "classes1.dex"; drv = dummy-classes-dex; } { name = "classes2.dex"; drv = actual-classes-dex; } ]; }; # classes10.dex vs classes2.dex shdw4 = make-apk { package = "com.example.shadowing.four"; name = "Shadowing 4"; classes = [ { name = "classes.dex"; drv = main-classes-dex; } { name = "classes2.dex"; drv = actual-classes-dex; } { name = "classes3.dex"; drv = empty-classes-dex; } { name = "classes4.dex"; drv = empty-classes-dex; } { name = "classes5.dex"; drv = empty-classes-dex; } { name = "classes6.dex"; drv = empty-classes-dex; } { name = "classes7.dex"; drv = empty-classes-dex; } { name = "classes8.dex"; drv = empty-classes-dex; } { name = "classes9.dex"; drv = empty-classes-dex; } { name = "classes10.dex"; drv = dummy-classes-dex; } ]; }; default = pkgs.symlinkJoin { name = "all-shadowing-apks"; paths = [ control shdw1 shdw2 shdw3 shdw4]; }; }; devShells.default = pkgs.mkShellNoCC { packages = [ jdk android-env.androidsdk ]; #shellHook = '' #''; }; }) ; }