#!/usr/bin/env bash # Modified by vii from komaruworld/mocktail. See README "About this fork". # Copyright 2026 Mocktail Project Authors # SPDX-License-Identifier: Apache-2.0 set -Eeuo pipefail readonly ROOT="${1:?source root is required}" readonly CXX="${2:-c++}" readonly TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mocktail-anylinux-test.XXXXXX")" trap 'rm -rf -- "${TEMP_DIR}"' EXIT source "${ROOT}/scripts/package_anylinux.sh" export UPINFO="" mkdir -p -- "${TEMP_DIR}/fixtures" # Compile the real adapter loader and updater canary. The fake runtime # deliberately has no Android adapters next to its relocated executable. "${CXX}" -std=c++17 -I"${ROOT}/include" -x c++ - \ "${ROOT}/src/graphics/bionic_egl_bridge.cc" \ "${ROOT}/src/runtime/supported_launch_policy.cc" \ "${ROOT}/src/update/readiness_canary.cc" -ldl -pthread \ -o "${TEMP_DIR}/fixtures/mocktail" <<'EOF' #include #include #include #include #include #include #include "mocktail/graphics/bionic_egl_bridge.h" #include "runtime/supported_launch_policy.h" #include "update/readiness_canary.h" int main(int argc, char** argv) { std::string error; if (!mocktail::runtime::ApplySupportedLaunchPolicy(false, &error)) return 4; const char* manifest = std::getenv("MOCKTAIL_COMPATIBILITY_MANIFEST"); if (!manifest || std::string(manifest).empty()) return 5; std::cout << "manifest=" << manifest << std::endl; mocktail::graphics::BionicEglBridge bridge; if (!bridge.Load()) { std::cerr << bridge.error() << '\n'; return 1; } const char* directory = std::getenv("MOCKTAIL_RUNTIME_LIBRARY_DIR"); if (!directory || bridge.library_path() != std::filesystem::path(directory) / "libEGL.so") return 2; std::cout << "adapter=" << bridge.library_path() << std::endl; if (argc == 2 && std::string(argv[1]) == "--help") { mocktail::update::CanaryOptions options; options.runtime_binary = argv[0]; options.payload_directory = directory; options.compatibility_manifest = std::filesystem::path(directory) / "candidate.json"; options.cache_root = std::getenv("MOCKTAIL_TEST_CANARY_ROOT"); options.state_root = options.cache_root / "state"; options.timeout_seconds = 5; const auto result = mocktail::update::RunReadinessCanary(options); std::ifstream log(result.log_path); const std::string contents((std::istreambuf_iterator(log)), {}); std::cout << contents; // Only test loading through the real sanitized child environment; // synthetic output is not proof of rendered-frame readiness. return result.exit_code == 0 && contents.find("manifest=" + options.compatibility_manifest.string()) != std::string::npos && contents.find("canary adapter loaded") != std::string::npos ? 0 : 3; } std::cout << "canary adapter loaded\n"; } EOF "${CXX}" -std=c++17 -shared -fPIC "${ROOT}/stubs/libegl_stub.cc" \ -ldl -o "${TEMP_DIR}/fixtures/libEGL.so" # The packaging contract uses a recording quick-sharun, without changing # the host /usr or downloading toolchain dependencies during a unit test. cat >"${TEMP_DIR}/quick-sharun" <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail if [[ "$1" == --make-appimage ]]; then [[ "$#" == 1 && -f "${APPDIR}/AppRun.sh" ]] [[ "${APPIMAGETOOL}" == /* && -x "${APPIMAGETOOL}" ]] printf 'make-appimage\n' >>"${MOCKTAIL_PACKAGING_FIXTURES}/calls" mkdir -p -- "${OUTPATH}" cp -- "$(type -P true)" "${OUTPATH}/${OUTNAME}" if [[ -n "${UPINFO:-}" ]]; then printf '%s\n' "${UPINFO}" >"${OUTPATH}/update-info" case "${MOCKTAIL_TEST_ZSYNC:-present}" in present) printf 'Filename: %s\nURL: %s\n\n\0checksum fixture' "${OUTNAME}" "${OUTNAME}" \ >"${OUTPATH}/${OUTNAME}.zsync" cp -- "${OUTPATH}/${OUTNAME}.zsync" "${OUTPATH}/expected.zsync" ;; empty) touch "${OUTPATH}/${OUTNAME}.zsync" ;; missing) : ;; esac fi exit 0 fi [[ "$#" == 7 && "$1" == /usr/bin/mocktail && "$2" == /usr/lib/mocktail/mocktail_webview_helper && "$3" == /usr/lib/mocktail/mocktail_failure_dialog && "$4" == /usr/lib/mocktail/mocktail_updater && "$5" == /usr/lib/mocktail && "$6" == /usr/share/mocktail && "$7" == /usr/bin/bash ]] [[ "${DESKTOP}" == /usr/share/applications/io.github.CoderDayton.nightcap.desktop ]] [[ "${MAIN_BIN}" == mocktail && "${STRACE_MODE}" == 1 ]] [[ "${STRACE_BINARY}" == mocktail && "${STRACE_FLAGS}" == --help ]] [[ -z "${NO_STRIP:-}" ]] if [[ -n "${SHARUN_LINK:-}" ]]; then wget -qO "${MOCKTAIL_PACKAGING_FIXTURES}/downloaded-sharun" "${SHARUN_LINK}" cmp "${MOCKTAIL_ANYLINUX_SHARUN}" "${MOCKTAIL_PACKAGING_FIXTURES}/downloaded-sharun" wget -qO "${MOCKTAIL_PACKAGING_FIXTURES}/downloaded-source" "${ANYLINUX_LIB_SOURCE}" cmp "${MOCKTAIL_ANYLINUX_C_SOURCE}" "${MOCKTAIL_PACKAGING_FIXTURES}/downloaded-source" fi # Only runtime metadata may be preseeded: no libs, custom AppRun, or bundle. [[ "$(find "${APPDIR}" -mindepth 1 -maxdepth 1 -printf '%f\n')" == .env ]] printf 'deploy-usr\n' >>"${MOCKTAIL_PACKAGING_FIXTURES}/calls" mkdir -p "${APPDIR}/bin" "${APPDIR}/lib/mocktail" \ "${APPDIR}/lib/webkitgtk-6.0/injected-bundle" "${APPDIR}/share/mocktail/metadata" cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/mocktail" "${APPDIR}/bin/mocktail" cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/libEGL.so" "${APPDIR}/lib/mocktail/libEGL.so" cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/libEGL.so" "${APPDIR}/lib/mocktail/libvulkan.so" cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/libEGL.so" "${APPDIR}/lib/mocktail/libmediandk.so" for metadata in roblox_compatibility roblox_bootstrap_sources \ roblox_host_abi_reference roblox_signing_certificates; do printf '{}\n' >"${APPDIR}/share/mocktail/metadata/${metadata}.json" done for name in mocktail_updater mocktail_failure_dialog mocktail_webview_helper \ WebKitNetworkProcess WebKitWebProcess WebKitGPUProcess; do cp -- "$(type -P true)" "${APPDIR}/bin/${name}" done for name in WebKitNetworkProcess WebKitWebProcess WebKitGPUProcess; do ln -s -- "../../bin/${name}" "${APPDIR}/lib/webkitgtk-6.0/${name}" done printf 'injected bundle fixture\n' \ >"${APPDIR}/lib/webkitgtk-6.0/injected-bundle/libwebkitgtkinjectedbundle.so" printf '#!/bin/sh\n' >"${APPDIR}/AppRun.sh" printf '#!/bin/sh\n' >"${APPDIR}/AppRun.lib" EOF chmod 0755 "${TEMP_DIR}/quick-sharun" mkdir -p "${TEMP_DIR}/fake-bin" "${TEMP_DIR}/local inputs" cat >"${TEMP_DIR}/fake-bin/wget" <<'EOF' #!/usr/bin/env bash printf 'unadapted wget must not be used for local inputs\n' >&2 exit 1 EOF chmod 0755 "${TEMP_DIR}/fake-bin/wget" cp -- "$(type -P true)" "${TEMP_DIR}/fake-bin/fixture-appimagetool" cp -- "$(type -P true)" "${TEMP_DIR}/local inputs/sharun" printf 'local source fixture\n' >"${TEMP_DIR}/local inputs/anylinux.c" export PATH="${TEMP_DIR}/fake-bin:${PATH}" export MOCKTAIL_ANYLINUX_SHARUN="${TEMP_DIR}/local inputs/sharun" export MOCKTAIL_ANYLINUX_C_SOURCE="${TEMP_DIR}/local inputs/anylinux.c" export MOCKTAIL_PACKAGING_FIXTURES="${TEMP_DIR}/fixtures" ANYLINUX_TOOL="${TEMP_DIR}/quick-sharun" ANYLINUX_APPIMAGETOOL="${TEMP_DIR}/fake-bin/fixture-appimagetool" ANYLINUX_WORK="${TEMP_DIR}/packaging" ANYLINUX_OUTPUT="${TEMP_DIR}/Mocktail.AppImage" mkdir -p -- "${ANYLINUX_WORK}" AnyLinuxVerifyInstalled() { :; } AnyLinuxDeploy [[ -x "${ANYLINUX_OUTPUT}" ]] [[ ! -e "${ANYLINUX_OUTPUT}.zsync" ]] [[ "$(<"${TEMP_DIR}/fixtures/calls")" == $'deploy-usr\nmake-appimage' ]] for filename in mocktail-nightly.AppImage mocktail-nightly-aarch64.AppImage \ Mocktail-1.0.5-x86_64.AppImage; do ( ANYLINUX_WORK="${TEMP_DIR}/updates/${filename}" ANYLINUX_OUTPUT="${TEMP_DIR}/${filename}" export UPINFO="gh-releases-zsync|owner|repository|continuous|${filename}.zsync" if [[ "${filename}" == Mocktail-* ]]; then UPINFO='gh-releases-zsync|owner|repository|latest|Mocktail-*-x86_64.AppImage.zsync' fi AnyLinuxDeploy [[ "$(<"${ANYLINUX_WORK}/image/update-info")" == "${UPINFO}" ]] cmp -- "${ANYLINUX_WORK}/image/expected.zsync" "${ANYLINUX_OUTPUT}.zsync" grep -aFxq "URL: ${filename}" "${ANYLINUX_OUTPUT}.zsync" [[ ! -e "${ANYLINUX_WORK}/image/${filename}.zsync" ]] ) done for sidecar in missing empty; do if ( ANYLINUX_WORK="${TEMP_DIR}/updates/${sidecar}" ANYLINUX_OUTPUT="${TEMP_DIR}/${sidecar}.AppImage" export UPINFO="gh-releases-zsync|owner|repository|continuous|${sidecar}.AppImage.zsync" export MOCKTAIL_TEST_ZSYNC="${sidecar}" AnyLinuxDeploy ) >"${TEMP_DIR}/${sidecar}.log" 2>&1; then printf 'packager accepted a %s .zsync file\n' "${sidecar}" >&2 exit 1 fi grep -Fq 'no .zsync was generated' "${TEMP_DIR}/${sidecar}.log" [[ ! -e "${TEMP_DIR}/${sidecar}.AppImage" ]] done mv -- "${ANYLINUX_WORK}/AppDir" "${TEMP_DIR}/relocated AppDir" app_dir="${TEMP_DIR}/relocated AppDir" AnyLinuxVerifyAppDir "${app_dir}" output="$( export SHARUN_DIR="${app_dir}" MOCKTAIL_TEST_CANARY_ROOT="${TEMP_DIR}/canary" export MOCKTAIL_RUNTIME_LIBRARY_DIR="${TEMP_DIR}/stale-runtime" export XDG_CACHE_HOME="${TEMP_DIR}/user-cache" export MOCKTAIL_COMPATIBILITY_MANIFEST="${TEMP_DIR}/candidate-override.json" set -a source "${app_dir}/.env" set +a [[ "${USE_HOST_XDG_CACHE_HOME}" == 1 && "${XDG_CACHE_HOME}" == "${TEMP_DIR}/user-cache" ]] [[ "${MOCKTAIL_WEBVIEW_HELPER}" == "${app_dir}/bin/mocktail_webview_helper" ]] [[ "${MOCKTAIL_UPDATE_SIGNING_TRUST_PATH}" == \ "${app_dir}/share/mocktail/metadata/roblox_signing_certificates.json" ]] [[ "${MOCKTAIL_COMPATIBILITY_MANIFEST}" == "${TEMP_DIR}/candidate-override.json" ]] [[ "${MOCKTAIL_PACKAGED_COMPATIBILITY_MANIFEST}" == \ "${app_dir}/share/mocktail/metadata/roblox_compatibility.json" ]] "${MOCKTAIL_BIN}" --help )" grep -Fxq "adapter=${app_dir}/lib/mocktail/libEGL.so" <<<"${output}" grep -Fxq 'canary adapter loaded' <<<"${output}" mkdir "${app_dir}/usr" if (AnyLinuxVerifyAppDir "${app_dir}") >"${TEMP_DIR}/unexpected-usr.log" 2>&1; then printf 'packager accepted an old /usr-prefixed AppDir\n' >&2 exit 1 fi rmdir "${app_dir}/usr" mv -- "${app_dir}/bin/WebKitNetworkProcess" "${TEMP_DIR}/WebKitNetworkProcess" if (AnyLinuxVerifyAppDir "${app_dir}") >"${TEMP_DIR}/missing-process.log" 2>&1; then printf 'packager accepted a missing WebKit process\n' >&2 exit 1 fi grep -Fq 'WebKitNetworkProcess' "${TEMP_DIR}/missing-process.log" mv -- "${TEMP_DIR}/WebKitNetworkProcess" "${app_dir}/bin/WebKitNetworkProcess" mv -- "${app_dir}/share/mocktail/metadata/roblox_signing_certificates.json" \ "${TEMP_DIR}/signing-certificates.json" if (AnyLinuxVerifyAppDir "${app_dir}") >"${TEMP_DIR}/missing-metadata.log" 2>&1; then printf 'packager accepted missing signature verification metadata\n' >&2 exit 1 fi grep -Fq 'roblox_signing_certificates.json' "${TEMP_DIR}/missing-metadata.log" prefix="${TEMP_DIR}/install/usr" mkdir -p -- "${prefix}/lib/mocktail" printf 'FreeBSD helper fixture\n' >"${prefix}/lib/mocktail/mocktail_freebsd_socket_helper" AnyLinuxPrepareInstallTree "${prefix}" [[ ! -e "${prefix}/lib/mocktail/mocktail_freebsd_socket_helper" ]] grep -Fxq 'FreeBSD helper fixture' \ "${prefix}/share/mocktail/helpers/mocktail_freebsd_socket_helper" # Exercise public AnyLinux dispatch and the unprivileged install path. A fake CMake # installer refuses to write without DESTDIR; the real bwrap overlay must # make those files visible under /usr to the real installed-layout checks. if command -v bwrap >/dev/null && bwrap --ro-bind / / -- /usr/bin/true 2>/dev/null; then mkdir -p "${TEMP_DIR}/fake-bin" "${TEMP_DIR}/build" touch "${TEMP_DIR}/build/cmake_install.cmake" cat >"${TEMP_DIR}/fake-bin/cmake" <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail [[ "$#" == 4 && "$1" == --install && "$3" == --prefix && "$4" == /usr ]] [[ -n "${DESTDIR:-}" && "${DESTDIR}" == */install ]] prefix="${DESTDIR}/usr" mkdir -p "${prefix}/bin" "${prefix}/lib/mocktail" "${prefix}/share/mocktail/metadata" cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/mocktail" "${prefix}/bin/mocktail" for helper in mocktail_updater mocktail_failure_dialog mocktail_webview_helper; do cp -- "$(type -P true)" "${prefix}/lib/mocktail/${helper}" done for adapter in libEGL.so libvulkan.so libmediandk.so; do cp -- "${MOCKTAIL_PACKAGING_FIXTURES}/libEGL.so" "${prefix}/lib/mocktail/${adapter}" done for metadata in roblox_compatibility roblox_bootstrap_sources \ roblox_host_abi_reference roblox_signing_certificates; do printf '{}\n' >"${prefix}/share/mocktail/metadata/${metadata}.json" done EOF chmod 0755 "${TEMP_DIR}/fake-bin/cmake" PATH="${TEMP_DIR}/fake-bin:${PATH}" \ MOCKTAIL_APPIMAGE_FORMAT=anylinux \ MOCKTAIL_ANYLINUX_PACKAGER="${TEMP_DIR}/quick-sharun" \ MOCKTAIL_ANYLINUX_APPIMAGETOOL=fixture-appimagetool \ MOCKTAIL_ANDROID_BUILD_TOOLS_ROOT="${TEMP_DIR}/not-installed" \ "${ROOT}/scripts/package_portable.sh" --build-dir "${TEMP_DIR}/build" \ --libc glibc --mode full --output "${TEMP_DIR}/no-portable-tree" \ --appimage "${TEMP_DIR}/overlay.AppImage" [[ -x "${TEMP_DIR}/overlay.AppImage" ]] [[ ! -e "${TEMP_DIR}/no-portable-tree" ]] else printf 'SKIP: private /usr overlay test needs unprivileged bwrap\n' fi printf 'AnyLinux packaging test passed\n'