diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 364ae96e8..99880829c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,7 +80,7 @@ jobs: - name: Stop gradle daemon run: ./gradlew --stop - test: + avd-test: name: Test x86_64 on API ${{ matrix.version }} runs-on: ubuntu-latest needs: build @@ -122,7 +122,7 @@ jobs: AVD_TEST_VERBOSE: 1 run: scripts/avd_test.sh ${{ matrix.version }} ${{ matrix.type }} - test-32: + avd-test-32: name: Test x86 on API ${{ matrix.version }} runs-on: ubuntu-latest needs: build @@ -160,3 +160,47 @@ jobs: FORCE_32_BIT: 1 AVD_TEST_VERBOSE: 1 run: scripts/avd_test.sh ${{ matrix.version }} + + cf_test: + name: Test on AOSP main Cuttlefish + runs-on: ubuntu-24.04 + env: + CF_HOME: /home/runner/aosp_cf_x86_64_phone + strategy: + fail-fast: false + + steps: + - name: Check out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Cuttlefish environment + run: | + scripts/cuttlefish.sh setup + scripts/cuttlefish.sh download + + - name: Wait for build artifacts + uses: lewagon/wait-on-check-action@v1.3.4 + timeout-minutes: 45 + with: + ref: ${{ github.ref }} + check-name: 'Build Magisk artifacts' + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ github.sha }} + path: out + + - name: Run Cuttlefish test + run: su $USER -c 'scripts/cuttlefish.sh test' + + - name: Upload logs on error + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: "cvd-logs" + path: | + /home/runner/aosp_cf_x86_64_phone/cuttlefish/instances/cvd-1/logs + /home/runner/aosp_cf_x86_64_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json diff --git a/build.py b/build.py index 378c78912..4cccb0606 100755 --- a/build.py +++ b/build.py @@ -566,6 +566,9 @@ def setup_ndk(args): def push_files(args, script): abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"]) + if not abi: + error("Cannot detect emulator ABI") + apk = Path( config["outdir"], ("app-release.apk" if args.release else "app-debug.apk") ) diff --git a/scripts/avd_test.sh b/scripts/avd_test.sh index 0355612bb..ff98c449a 100755 --- a/scripts/avd_test.sh +++ b/scripts/avd_test.sh @@ -2,27 +2,15 @@ emu="$ANDROID_SDK_ROOT/emulator/emulator" avd="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/avdmanager" -sdk="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" emu_args_base='-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -read-only -no-snapshot' lsposed_url='https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip' -boot_timeout=600 emu_pid= -export PATH="$PATH:$ANDROID_SDK_ROOT/platform-tools" - atd_min_api=30 atd_max_api=34 lsposed_min_api=27 huge_ram_min_api=26 -print_title() { - echo -e "\n\033[44;39m${1}\033[0m\n" -} - -print_error() { - echo -e "\n\033[41;39m${1}\033[0m\n" -} - cleanup() { print_error "! An error occurred when testing $pkg" @@ -72,19 +60,6 @@ wait_emu() { [ $which_pid -eq $wait_pid ] } -run_content_cmd() { - while true; do - local out=$(adb shell echo "'content call --uri content://com.topjohnwu.magisk.provider --method $1'" \| /system/xbin/su | tee /dev/fd/2) - if ! grep -q 'Bundle\[' <<< "$out"; then - # The call failed, wait a while and retry later - sleep 30 - else - grep -q 'result=true' <<< "$out" - return $? - fi - done -} - test_emu() { local variant=$1 local api=$2 @@ -102,13 +77,7 @@ test_emu() { wait_emu wait_for_boot - adb shell 'PATH=$PATH:/debug_ramdisk magisk -v' - - # Install the Magisk app - adb install -r -g out/app-${variant}.apk - - # Use the app to run setup and reboot - run_content_cmd setup + test_setup $variant # Install LSPosed if [ $api -ge $lsposed_min_api -a $api -le $atd_max_api ]; then @@ -122,9 +91,7 @@ test_emu() { fi wait_emu wait_for_boot - # Run app tests - run_content_cmd test - adb shell echo 'su -c id' \| /system/xbin/su 2000 | tee /dev/fd/2 | grep -q 'uid=0' + test_app # Try to launch LSPosed if [ $api -ge $lsposed_min_api -a $api -le $atd_max_api ]; then @@ -169,7 +136,6 @@ run_test() { local pkg="system-images;android-$ver;$type;$arch" local img_dir="$ANDROID_SDK_ROOT/system-images/android-$ver/$type/$arch" local ramdisk="$img_dir/ramdisk.img" - local features="$img_dir/advancedFeatures.ini" # Old Linux kernels will not boot with memory larger than 3GB local memory @@ -203,7 +169,7 @@ run_test() { wait $emu_pid test_emu debug $api - # Re-patch and test release build + # Patch and test release build ./build.py -r avd_patch -s "$ramdisk" magisk_patched.img kill -INT $emu_pid wait $emu_pid @@ -215,13 +181,13 @@ run_test() { rm -f magisk_patched.img } +set -xe trap cleanup EXIT +. scripts/test_common.sh export -f wait_for_boot export -f wait_for_bootanim -set -xe - case $(uname -m) in 'arm64'|'aarch64') arch=arm64-v8a diff --git a/scripts/cuttlefish.sh b/scripts/cuttlefish.sh new file mode 100755 index 000000000..fd530ca0b --- /dev/null +++ b/scripts/cuttlefish.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +cvd_args='-daemon -enable_sandbox=false -report_anonymous_usage_stats=n' +magisk_args='-init_boot_image=magisk_patched.img' + +cleanup() { + print_error "! An error occurred" + run_cvd_bin stop_cvd || true + rm -f magisk_patched.img* +} + +run_cvd_bin() { + local exe=$1 + shift + HOME=$CF_HOME $CF_HOME/bin/$exe "$@" +} + +install_bazel() { + sudo apt-get install -y apt-transport-https curl gnupg + curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg + sudo mv bazel-archive-keyring.gpg /usr/share/keyrings + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list + sudo apt-get update && sudo apt-get install bazel zip unzip +} + +build_cf() { + git clone https://github.com/google/android-cuttlefish + cd android-cuttlefish + # We only want to build the base package + sed -i '$ d' tools/buildutils/build_packages.sh + tools/buildutils/build_packages.sh + sudo dpkg -i ./cuttlefish-base_*_*64.deb || sudo apt-get install -f + cd ../ + rm -rf android-cuttlefish +} + +setup_env() { + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger + sudo usermod -aG kvm,cvdnetwork,render $USER + yes | "$sdk" --licenses > /dev/null + "$sdk" --channel=3 tools platform-tools +} + +download_cf() { + local build_id=$(curl -sL https://ci.android.com/builds/branches/aosp-main/status.json | \ + jq -r '.targets[] | select(.name == "aosp_cf_x86_64_phone-trunk_staging-userdebug") | .last_known_good_build') + local sys_img_url="https://ci.android.com/builds/submitted/${build_id}/aosp_cf_x86_64_phone-trunk_staging-userdebug/latest/raw/aosp_cf_x86_64_phone-img-${build_id}.zip" + local host_pkg_url="https://ci.android.com/builds/submitted/${build_id}/aosp_cf_x86_64_phone-trunk_staging-userdebug/latest/raw/cvd-host_package.tar.gz" + + print_title "* Download aosp-main ($build_id) system images" + curl -L $sys_img_url -o aosp_cf_x86_64_phone-img.zip + curl -LO $host_pkg_url + rm -rf $CF_HOME + mkdir -p $CF_HOME + tar xvf cvd-host_package.tar.gz -C $CF_HOME + unzip aosp_cf_x86_64_phone-img.zip -d $CF_HOME + rm -f cvd-host_package.tar.gz aosp_cf_x86_64_phone-img.zip +} + +test_cf() { + local variant=$1 + + run_cvd_bin stop_cvd || true + + print_title "* Testing $variant builds" + timeout $boot_timeout bash -c "run_cvd_bin launch_cvd $cvd_args $magisk_args -resume=false" + adb wait-for-device + test_setup $variant + + adb reboot + sleep 5 + run_cvd_bin stop_cvd || true + + timeout $boot_timeout bash -c "run_cvd_bin launch_cvd $cvd_args $magisk_args" + adb wait-for-device + test_app +} + +run_test() { + # Launch stock cuttlefish + run_cvd_bin launch_cvd $cvd_args -resume=false + adb wait-for-device + + # Patch and test debug build + ./build.py avd_patch -s "$CF_HOME/init_boot.img" magisk_patched.img + test_cf debug + + # Patch and test release build + ./build.py -r avd_patch -s "$CF_HOME/init_boot.img" magisk_patched.img + test_cf release + + # Cleanup + run_cvd_bin stop_cvd || true + rm -f magisk_patched.img* +} + +set -xe +. scripts/test_common.sh + +if [ -z $CF_HOME ]; then + print_error "! Environment variable CF_HOME is required" + exit 1 +fi + +case "$1" in + setup ) + install_bazel + build_cf + setup_env + ;; + download ) + download_cf + ;; + test ) + trap cleanup EXIT + export -f run_cvd_bin + run_test + trap - EXIT + ;; + * ) + exit 1 + ;; +esac diff --git a/scripts/test_common.sh b/scripts/test_common.sh new file mode 100644 index 000000000..b90e6bdf8 --- /dev/null +++ b/scripts/test_common.sh @@ -0,0 +1,42 @@ +export PATH="$PATH:$ANDROID_SDK_ROOT/platform-tools" + +sdk="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" +boot_timeout=600 + +print_title() { + echo -e "\n\033[44;39m${1}\033[0m\n" +} + +print_error() { + echo -e "\n\033[41;39m${1}\033[0m\n" +} + +run_content_cmd() { + while true; do + local out=$(adb shell echo "'content call --uri content://com.topjohnwu.magisk.provider --method $1'" \| /system/xbin/su | tee /dev/fd/2) + if ! grep -q 'Bundle\[' <<< "$out"; then + # The call failed, wait a while and retry later + sleep 30 + else + grep -q 'result=true' <<< "$out" + return $? + fi + done +} + +test_setup() { + local variant=$1 + adb shell 'PATH=$PATH:/debug_ramdisk magisk -v' + + # Install the Magisk app + adb install -r -g out/app-${variant}.apk + + # Use the app to run setup and reboot + run_content_cmd setup +} + +test_app() { + # Run app tests + run_content_cmd test + adb shell echo 'su -c id' \| /system/xbin/su 2000 | tee /dev/fd/2 | grep -q 'uid=0' +}