#!/usr/bin/env bash is_mac() { if [ "$(uname -s)" != "Darwin" ]; then echo "You need a mac to build this package" exit 1 fi } CURDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION=$(git describe HEAD) # Only append date if we're not on a whole version, like v2018.11 if echo "${VERSION}" | grep -; then VERSION=${VERSION}_$(git describe HEAD | xargs git show -s --format=format:"%cd" --date=short) fi # Default Android build to arm. ANDROID_ARCH="${ANDROID_ARCH:-arm}" # Default to Android 4.0+; required for NDK 15 but with a custom NDK the strict minimum is 9. NDKABI=${NDKABI:-14} export NDKABI # Default android flavor ANDROID_FLAVOR=${ANDROID_FLAVOR:-rocks} export ANDROID_FLAVOR function assert_ret_zero() { if [ "${1}" -ne 0 ]; then if [ -n "${2}" ]; then echo "${2}" fi exit 1 fi } function check_submodules() { if git submodule status | grep -qE '^\-'; then kodev-fetch-thirdparty fi } # Takes two arguments: # $1 arguments to pass to pgrep # $2 process name to pgrep for function gnuplot_wrapper() { # inspired by https://gist.github.com/nicolasazrak/32d68ed6c845a095f75f037ecc2f0436 trap capture_ctrl_c INT TEMP_DIR=$(mktemp --directory /tmp/tmp.koreaderXXX) LOG="${TEMP_DIR}/memory.log" SCRIPT_PNG="${TEMP_DIR}/script_png.p" SCRIPT_SHOW="${TEMP_DIR}/script_show.p" IMAGE_PNG="${TEMP_DIR}/graph.png" echo "Memory plot output to ${TEMP_DIR}" cat >"${SCRIPT_PNG}" <"${SCRIPT_SHOW}" <"${LOG}" while true; do # shellcheck disable=SC2086 ps -p "$(pgrep --delimiter ' ' $1 "$2")" -o pid=,vsz=,rss= >>"${LOG}" sleep 1 done & LOOP_PID=$! gnuplot "${SCRIPT_SHOW}" & GNUPLOT_PID=$! } function setup_env() { SETUP_ENV_GREP_COMMAND="grep -z -v debug" if [ -n "${KODEBUG}" ]; then SETUP_ENV_GREP_COMMAND="grep -z debug" # for android adb install KODEBUG_SUFFIX=-debug fi local files=() while IFS= read -r -d $'\0'; do files+=("${REPLY}") done < <(find . -maxdepth 1 -name 'koreader-emulator-*' -print0 | ${SETUP_ENV_GREP_COMMAND}) test ${#files[@]} -gt 0 assert_ret_zero $? "Emulator not found. Please build it first." local idx=0 # Warn if multiple matches were found if [ ${#files[@]} -gt 1 ]; then echo "Multiple emulator builds found:" local ts=() # Store list of ts at the same index for i in "${!files[@]}"; do local file="${files[${i}]}/koreader" if [ -d "${file}" ]; then echo "${file} (last modified on $(stat -c %y "${file}"))" ts[${i}]="$(stat -c %Y "${file}")" fi done # Sort the list of ts local sorted_ts=() IFS=$'\n' read -d '' -r -a sorted_ts < <(printf '%s\n' "${ts[@]}" | sort -r) # Find the id of the most recent ts (spoiler: it's going to be the one currently targeted by this invocation of kodev) for i in "${!ts[@]}"; do if [ "${ts[${i}]}" == "${sorted_ts[0]}" ]; then idx="${i}" break fi done # Recap echo "Picking the most recent one: ${files[${idx}]}/koreader" fi EMU_DIR="${files[${idx}]}/koreader" export EMU_DIR LD_LIBRARY_PATH=$(realpath "${EMU_DIR}")/libs:${LD_LIBRARY_PATH} export LD_LIBRARY_PATH } function kodev-fetch-thirdparty() { make fetchthirdparty } SUPPORTED_TARGETS=" kindle Compatible with all Kindle models >= Kindle4 kindlepw2 With compiler optimizations for Kindle models >= Paperwhite 2 kindle-legacy Needed only for Kindle2/3/DXG kobo cervantes remarkable sony-prstux android pocketbook ubuntu-touch appimage debian Debian package for current arch debian-armel Debian package for generic armel devices debian-armhf Debian package for generic armhf devices macos MacOS app bundle. You need a mac to build this package emu (*default) If no TARGET is given, assume emulator win32 " function kodev-build() { BUILD_HELP_MSG=" usage: build OPTIONS: -v, --verbose Build in verbose mode. --debug include debugging symbols (default for emulator) --no-debug no debugging symbols (default for target devices) TARGET: ${SUPPORTED_TARGETS}" while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in -v | --verbose) export VERBOSE=1 ;; -h | --help) echo "${BUILD_HELP_MSG}" exit 0 ;; --no-debug) export KODEBUG= KODEBUG_NO_DEFAULT=1 ;; --debug) export KODEBUG=1 KODEBUG_NO_DEFAULT=1 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${BUILD_HELP_MSG}" exit 1 ;; esac shift 1 done check_submodules case "${1}" in cervantes) make TARGET=cervantes assert_ret_zero $? ;; kindle) make TARGET=kindle assert_ret_zero $? ;; kindlepw2) make TARGET=kindlepw2 assert_ret_zero $? ;; kobo) make TARGET=kobo assert_ret_zero $? ;; remarkable) make TARGET=remarkable assert_ret_zero $? ;; sony-prstux) make TARGET=sony-prstux assert_ret_zero $? ;; kindle-legacy) make TARGET=kindle-legacy assert_ret_zero $? ;; android) if [ -z "${NDK}" ]; then if [ -n "${ANDROID_NDK_HOME}" ]; then # some distributions use `ANDROID_NDK` instead, fall back to it export NDK="${ANDROID_NDK_HOME}" else export NDK="${CURDIR}/base/toolchain/android-ndk-r15c" fi fi [ -e "${CURDIR}/base/toolchain/android-toolchain-${ANDROID_ARCH}/bin/" ] || { { [ -e "${NDK}" ] || make -C "${CURDIR}/base/toolchain" android-ndk; } assert_ret_zero $? make android-toolchain assert_ret_zero $? } echo "Using NDK: ${NDK}..." make TARGET=android assert_ret_zero $? ;; pocketbook) make TARGET=pocketbook assert_ret_zero $? ;; ubuntu-touch) make TARGET=ubuntu-touch assert_ret_zero $? ;; appimage) make TARGET=appimage assert_ret_zero $? ;; debian) make TARGET=debian assert_ret_zero $? ;; debian-armel) make TARGET=debian-armel assert_ret_zero $? ;; debian-armhf) make TARGET=debian-armhf assert_ret_zero $? ;; macos) is_mac make TARGET=macos assert_ret_zero $? ;; win32) make TARGET=win32 assert_ret_zero $? ;; *) if [ -z "${KODEBUG_NO_DEFAULT}" ]; then # no explicit --debug / --no-debug # builds a debug build by default, like kodev-run export KODEBUG=1 fi make assert_ret_zero $? "Failed to build emulator! Try run with -v for more information." setup_env ;; esac } function kodev-clean() { CLEAN_HELP_MSG=" usage: clean TARGET: ${SUPPORTED_TARGETS}" while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in --no-debug) export KODEBUG= KODEBUG_NO_DEFAULT=1 ;; --debug) export KODEBUG=1 KODEBUG_NO_DEFAULT=1 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${BUILD_HELP_MSG}" exit 1 ;; esac shift 1 done case "${1}" in -h | --help) echo "${CLEAN_HELP_MSG}" exit 0 ;; cervantes) make TARGET=cervantes clean ;; kindle) make TARGET=kindle clean ;; kindlepw2) make TARGET=kindlepw2 clean ;; kobo) make TARGET=kobo clean ;; remarkable) make TARGET=remarkable clean ;; sony-prstux) make TARGET=sony-prstux clean ;; kindle-legacy) make TARGET=kindle-legacy clean ;; android) make TARGET=android clean rm -f ./*.apk ;; pocketbook) make TARGET=pocketbook clean ;; ubuntu-touch) make TARGET=ubuntu-touch clean ;; appimage) make TARGET=appimage clean ;; debian) make TARGET=debian clean ;; debian-armel) make TARGET=debian-armel clean ;; debian-armhf) make TARGET=debian-armhf clean ;; macos) is_mac make TARGET=macos clean ;; win32) make TARGET=win32 clean ;; *) if [ -z "${KODEBUG_NO_DEFAULT}" ]; then # no explicit --debug / --no-debug # builds a debug build by default, like kodev-run export KODEBUG=1 fi make clean ;; esac } function kodev-release() { # SUPPORTED_RELEASE_TARGETS=$(echo ${SUPPORTED_TARGETS} | sed 's/win32//') SUPPORTED_RELEASE_TARGETS="${SUPPORTED_TARGETS/emu*/""}" RELEASE_HELP_MSG=" usage: release OPTIONS: --debug debug-enabled release (for remote gdb) --ignore-translation do not fetch translation for release TARGET: ${SUPPORTED_RELEASE_TARGETS}" [ $# -lt 1 ] && { echo "${RELEASE_HELP_MSG}" exit 1 } ignore_translation=0 while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in --debug) export KODEBUG=1 ;; --ignore-translation) ignore_translation=1 ;; -v | --verbose) export VERBOSE=1 ;; -h | --help) echo "${RELEASE_HELP_MSG}" exit 0 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${RELEASE_HELP_MSG}" exit 1 ;; esac shift 1 done check_submodules if [ "${ignore_translation}" -eq 0 ]; then make po || { echo "ERROR: failed to fetch translation." echo "Tip: Use --ignore-translation OPTION if you want to build a release without translation." exit 1 } fi case "${1}" in kindle) kodev-build kindle make TARGET=kindle update ;; kindlepw2) kodev-build kindlepw2 make TARGET=kindlepw2 update ;; kobo) kodev-build kobo make TARGET=kobo update ;; remarkable) kodev-build remarkable make TARGET=remarkable update ;; sony-prstux) kodev-build sony-prstux make TARGET=sony-prstux update ;; cervantes) kodev-build cervantes make TARGET=cervantes update ;; kindle-legacy) kodev-build kindle-legacy make TARGET=kindle-legacy update ;; android) kodev-build android export PATH=${PATH}:${CURDIR}/base/toolchain/android-sdk-linux/tools command -v android &>/dev/null || { make -C "${CURDIR}/base/toolchain" android-sdk } ANDROID_HOME=$(dirname "$(dirname "$(command -v android)")") export ANDROID_HOME export PATH=${PATH}:${NDK} make TARGET=android update ;; pocketbook) kodev-build pocketbook make TARGET=pocketbook update ;; ubuntu-touch) kodev-build ubuntu-touch make TARGET=ubuntu-touch update ;; appimage) kodev-build appimage make TARGET=appimage update ;; debian) kodev-build debian make TARGET=debian update ;; debian-armel) kodev-build debian-armel make TARGET=debian-armel update ;; debian-armhf) kodev-build debian-armhf make TARGET=debian-armhf update ;; macos) is_mac kodev-build macos make TARGET=macos update ;; *) echo "Unsupported target for release: $1." echo "${RELEASE_HELP_MSG}" exit 1 ;; esac } function kodev-wbuilder() { kodev-build echo "[*] Running wbuilder.lua..." pushd "${EMU_DIR}" && { EMULATE_READER_W=540 EMULATE_READER_H=720 ./luajit ./tools/wbuilder.lua } && popd || exit } function kodev-run() { RUN_HELP_MSG=" usage: run OPTIONS: -h=X, --screen-height=X set height of the emulator screen (default: 720) -w=X, --screen-width=X set width of the emulator screen (default: 540) -d=X, --screen-dpi=X set DPI of the emulator screen (default: 160) --no-build run reader without rebuilding --no-debug no debugging symbols (requires rebuilding) --disable-touch use this if you want to simulate keyboard only devices -s=FOO --simulate=FOO simulate dimension and other specs for a given device model supported model: kobo-aura-one, kindle3, hidpi --gdb=X run with debugger (default: nemiver) --graph graph memory use (requires gnuplot) --valgrind=X run with valgrind (default: valgrind --trace-children=yes) TARGET: android install and run KOReader on an Android device connected through ADB " screen_width=540 screen_height=720 export KODEBUG=1 while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in --disable-touch) export DISABLE_TOUCH=1 ;; --no-build) no_build=1 ;; --no-debug) export KODEBUG= ;; --gdb) if [ -n "${VALUE}" ]; then gdb=${VALUE} else # Try to use friendly defaults for gdb if command -v nemiver >/dev/null; then # Nemiver is a nice GUI gdb=nemiver elif command -v ddd >/dev/null; then # DDD is a slightly less nice GUI gdb=ddd elif command -v cgdb >/dev/null; then # cgdb is a nice curses-based gdb front gdb=cgdb elif command -v gdb >/dev/null; then # gdb -tui is a slightly less nice terminal user interface gdb="gdb -tui" else echo "Couldn't find gdb." exit 1 fi fi ;; --graph) graph_memory=1 ;; --valgrind) if [ -n "${VALUE}" ]; then valgrind=${VALUE} else # Try to use sensible defaults for valgrind if command -v valgrind >/dev/null; then valgrind="valgrind --trace-children=yes" else echo "Couldn't find valgrind." exit 1 fi fi ;; -w | --screen-width) screen_width=${VALUE} ;; -h | --screen-height) # simple numeric check due to overlap between -h for help and height if [ -n "${VALUE##*[!0-9]*}" ]; then screen_height=${VALUE} else echo "${RUN_HELP_MSG}" exit 1 fi ;; -d | --screen-dpi) screen_dpi=${VALUE} ;; -s | --simulate) device_model=${VALUE} case "${device_model}" in kindle3) screen_width=600 screen_height=800 ;; kobo-aura-one) screen_width=1404 screen_height=1872 screen_dpi=300 ;; hidpi) screen_width=1500 screen_height=2000 screen_dpi=600 ;; *) echo "ERROR: spec unknown for ${device_model}." ;; esac ;; --help) echo "${RUN_HELP_MSG}" exit 0 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${RUN_HELP_MSG}" exit 1 ;; esac shift done case "${1}" in android) command -v adb >/dev/null && { if [ -z "${no_build}" ]; then echo "[*] Building KOReader for Android…" kodev-release --ignore-translation android assert_ret_zero $? fi setup_env # clear logcat to get rid of useless cruft adb logcat -c # uninstall existing package to make sure *everything* is gone from memory # no assert_ret_zero; uninstall is allowed to fail if there's nothing to uninstall adb uninstall "org.koreader.launcher" adb install "koreader-android-${ANDROID_ARCH}${KODEBUG_SUFFIX}-${VERSION}.apk" assert_ret_zero $? # there's no adb run so we do this… adb shell monkey -p org.koreader.launcher -c android.intent.category.LAUNCHER 1 assert_ret_zero $? adb logcat KOReader:V luajit-launcher:V "*:E" } || echo "Failed to find adb in PATH to interact with Android device." ;; *) if [ -z "${no_build}" ]; then echo "[*] Building KOReader…" if [ -z "${KODEBUG}" ]; then kodev-build --no-debug else kodev-build fi else setup_env fi if [ ! -d "${EMU_DIR}" ]; then echo "Failed to find emulator directory! Please try build command first." exit 1 fi if [ -n "${graph_memory}" ]; then gnuplot_wrapper "--parent $$" "reader.lua" fi # run with catchsegv by default when it is available # see https://github.com/koreader/koreader/issues/2878#issuecomment-326796777 if command -v catchsegv >/dev/null; then CATCHSEGV=$(command -v catchsegv) fi KOREADER_COMMAND="${CATCHSEGV} ./reader.lua -d" if [ -n "${gdb}" ]; then KOREADER_COMMAND="${gdb} ./luajit reader.lua -d" fi if [ -n "${valgrind}" ]; then KOREADER_COMMAND="${valgrind} ./luajit reader.lua -d" fi echo "[*] Running KOReader with arguments: $*..." pushd "${EMU_DIR}" && { if [ $# -lt 1 ]; then args=${CURDIR}/test else args="$*" [[ "${args}" != /* ]] && args="${CURDIR}/${args}" fi KOREADER_COMMAND="${KOREADER_COMMAND} ${args}" RETURN_VALUE=85 while [ "${RETURN_VALUE}" -eq 85 ]; do EMULATE_READER_W=${screen_width} EMULATE_READER_H=${screen_height} EMULATE_READER_DPI=${screen_dpi} \ ${KOREADER_COMMAND} RETURN_VALUE=$? done } && popd || exit if [ -n "${graph_memory}" ]; then capture_ctrl_c fi ;; esac } function kodev-test() { TEST_HELP_MSG=" usage: test [front|base] TEST_NAME is optional. If no TEST_NAME is given, all tests will be run. OPTIONS: --tags=TAGS only run tests with given tags --no-debug no debugging symbols (default for target devices) " while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in --graph) graph_memory=1 ;; --no-debug) export KODEBUG= KODEBUG_NO_DEFAULT=1 ;; --tags) opts="--tags=${VALUE}" ;; -h | --help) echo "${TEST_HELP_MSG}" exit 0 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${TEST_HELP_MSG}" exit 1 ;; esac shift done [ $# -lt 1 ] && { echo "${TEST_HELP_MSG}" exit 1 } [[ "${1}" != "front" && "${1}" != "base" ]] && { echo "Invalid test suite: $1!" echo "${TEST_HELP_MSG}" exit 1 } set -e check_submodules && kodev-build setup_env make "${EMU_DIR}/.busted" pushd "${EMU_DIR}" && { test_path_basedir="./spec/$1/unit" rm -rf "${test_path_basedir}"/data/*.sdr test_path="${test_path_basedir}" if [ -n "${2}" ]; then test_path="${test_path_basedir}/$2" fi echo "Running tests in" "${test_path}" busted --lua="./luajit" "${opts}" \ --output=gtest \ --lpath="${test_path_basedir}/?.lua" \ --exclude-tags=notest "${test_path}" } && popd || exit } function kodev-cov() { COV_HELP_MSG=" usage: cov OPTIONS: --show-previous show coverage stats from previous run --full show full coverage report (down to each line) --no-debug no debugging symbols (default for target devices) " show_full=0 show_previous=0 while [[ "${1}" == '-'* ]]; do PARAM=$(echo "${1}" | awk -F= '{print $1}') VALUE=$(echo "${1}" | awk -F= '{print $2}') case "${PARAM}" in --full) show_full=1 ;; --show-previous) show_previous=1 ;; --no-debug) export KODEBUG= KODEBUG_NO_DEFAULT=1 ;; -h | --help) echo "${COV_HELP_MSG}" exit 0 ;; *) echo "ERROR: unknown option \"${PARAM}\"" echo "${COV_HELP_MSG}" exit 1 ;; esac shift done set -e check_submodules && kodev-build setup_env make "${EMU_DIR}/.busted" pushd "${EMU_DIR}" && { target=front test_path="./spec/${target}/unit" if [ "${show_previous}" -eq 0 ]; then echo "Running tests in" ${test_path} busted --lua="./luajit" \ --sort-files \ -o "./spec/${target}/unit/verbose_print" \ --coverage \ --exclude-tags=nocov "${test_path}" || { echo "Failed to run tests!" && exit 1 } fi if [ "${show_full}" -eq 1 ]; then cat luacov.report.out else LUACOV_REPORT_SUMMARY=$(grep -nm1 -e '^Summary$' luacov.report.out | cut -d: -f1) tail -n \ +$((LUACOV_REPORT_SUMMARY - 1)) \ luacov.report.out fi } && popd || exit } function kodev-log() { LOG_HELP_MSG=" usage: log TARGET: android " [ $# -lt 1 ] && { echo "${LOG_HELP_MSG}" exit 1 } case "${1}" in -h | --help) echo "${LOG_HELP_MSG}" exit 0 ;; android) adb logcat 'luajit-launcher:D KOReader:D *:S' ;; *) echo "Unsupported target: $1." echo "${LOG_HELP_MSG}" exit 1 ;; esac } HELP_MSG=" usage: $0 COMMAND Supported commands: activate Bootstrap shell environment for kodev build Build KOReader clean Clean KOReader build fetch-thirdparty Fetch thirdparty dependencies for build log Tail log stream for a running KOReader app release Build KOReader release package run Run KOReader test Run busted tests check Run luacheck static-analysis cov Run busted tests for coverage wbuilder Run wbuilder.lua script (useful for building new UI widget) " [ $# -lt 1 ] && { echo "Missing command." echo "${HELP_MSG}" exit 1 } case "${1}" in activate) echo "adding ${CURDIR} to \$PATH..." export PATH="${PATH}:${CURDIR}" eval "$(luarocks path --bin)" exec "${SHELL}" ;; fetch-thirdparty) kodev-fetch-thirdparty ;; clean) shift 1 kodev-clean "$@" ;; build) shift 1 kodev-build "$@" ;; release) shift 1 kodev-release "$@" ;; wbuilder) kodev-wbuilder ;; run) shift 1 kodev-run "$@" ;; test) shift 1 kodev-test "$@" ;; check) luacheck -q {reader,setupkoenv,datastorage}.lua frontend plugins spec ;; cov) shift 1 kodev-cov "$@" ;; prompt) kodev-build pushd "${EMU_DIR}" && { ./luajit -i setupkoenv.lua } && popd || exit ;; log) shift 1 kodev-log "$@" ;; --help | -h) echo "${HELP_MSG}" exit 0 ;; *) echo "Unknown command: $1." echo "${HELP_MSG}" exit 1 ;; esac