diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 2b25c8ba3..2609e7700 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -70,6 +70,8 @@ jobs: cat android-release-artifacts.sha256 - name: Attest Android release artifacts + # Keep uploads/verifiers running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: android-release-artifacts.sha256 diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index ce84b81e6..393a3d48e 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -76,6 +76,8 @@ jobs: cat desktop-macos-artifacts.sha256 - name: Attest macOS release artifacts + # Keep uploads/verifiers running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: desktop-macos-artifacts.sha256 @@ -147,6 +149,8 @@ jobs: cat desktop-linux-artifacts.sha256 - name: Attest Linux release artifacts + # Keep uploads/verifiers running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: desktop-linux-artifacts.sha256 diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml index 27334d216..1efea1aab 100644 --- a/.github/workflows/desktop-pr-build.yml +++ b/.github/workflows/desktop-pr-build.yml @@ -113,6 +113,8 @@ jobs: with: name: maple-linux-pr path: | + frontend/src-tauri/target/release/bundle/appimage/*.AppImage + frontend/src-tauri/target/release/bundle/appimage/*.AppImage.sig frontend/src-tauri/target/release/bundle/deb/*.deb frontend/src-tauri/target/release/bundle/deb/*.deb.sig frontend/src-tauri/target/release/bundle/rpm/*.rpm diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index 0d6f86c67..20afe5906 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -89,6 +89,8 @@ jobs: cat ios-release-artifacts.sha256 - name: Attest iOS release artifacts + # Keep uploads/verifiers running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: ios-release-artifacts.sha256 @@ -161,12 +163,13 @@ jobs: - name: Prepare App Store Connect API key env: + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_PRIVATE_KEY: ${{ secrets.APPLE_API_PRIVATE_KEY }} run: | set -euo pipefail - if [ -z "${APPLE_API_KEY}" ] || [ -z "${APPLE_API_PRIVATE_KEY}" ]; then - echo "APPLE_API_KEY and APPLE_API_PRIVATE_KEY are required to submit to TestFlight." >&2 + if [ -z "${APPLE_API_ISSUER}" ] || [ -z "${APPLE_API_KEY}" ] || [ -z "${APPLE_API_PRIVATE_KEY}" ]; then + echo "APPLE_API_ISSUER, APPLE_API_KEY, and APPLE_API_PRIVATE_KEY are required to submit to TestFlight." >&2 exit 1 fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6677a68ce..7f1ac21a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -134,6 +134,8 @@ jobs: cat desktop-release-artifacts.sha256 - name: Attest desktop release artifacts + # Keep release uploads running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: desktop-release-artifacts.sha256 @@ -231,6 +233,8 @@ jobs: cat android-release-artifacts.sha256 - name: Attest Android release artifacts + # Keep release uploads running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: android-release-artifacts.sha256 @@ -339,6 +343,8 @@ jobs: cat ios-release-artifacts.sha256 - name: Attest iOS release artifacts + # Keep release uploads running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: ios-release-artifacts.sha256 @@ -400,6 +406,8 @@ jobs: run: cat frontend/src-tauri/target/reproducibility/web-final.sha256 - name: Attest web release artifact + # Keep release uploads running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: frontend/src-tauri/target/reproducibility/web-final.sha256 @@ -444,17 +452,19 @@ jobs: - name: Generate latest.json env: RELEASE_TAG: ${{ github.event.release.tag_name }} + MAPLE_LATEST_JSON_PUB_DATE: ${{ github.event.release.published_at || github.event.release.created_at }} run: nix develop .#ci -c ./scripts/ci/latest-json.sh artifacts latest.json - name: Collect latest.json checksums run: | nix develop .#ci -c ./scripts/ci/attestation-manifest.sh \ latest-json-artifacts.sha256 \ - frontend/src-tauri/target/reproducibility/latest-json-final.sha256 \ frontend/src-tauri/target/reproducibility/latest-json-final.sha256 cat latest-json-artifacts.sha256 - name: Attest latest.json + # Keep release uploads running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: latest-json-artifacts.sha256 diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index 88c7414a0..f0acc3cd8 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -78,6 +78,8 @@ jobs: run: cat frontend/src-tauri/target/reproducibility/web-final.sha256 - name: Attest web artifact + # Keep uploads/verifiers running if GitHub token policy rejects artifact attestation. + continue-on-error: true uses: actions/attest@281a49d4cbb0a72c9575a50d18f6deb515a11deb # was v4 with: subject-checksums: frontend/src-tauri/target/reproducibility/web-final.sha256 diff --git a/flake.nix b/flake.nix index 77e7f6f8b..14fbe59c6 100644 --- a/flake.nix +++ b/flake.nix @@ -127,7 +127,12 @@ while [ "''${#}" -gt 0 ]; do case "''${1}" in --toolchain | -t) - shift 2 + shift + if [ "''${#}" -eq 0 ]; then + echo "rustup is shimmed by the Nix shell and --toolchain requires a value." >&2 + exit 1 + fi + shift ;; --*) shift @@ -217,6 +222,38 @@ xdg-utils ]; + linuxdeploySupportPackages = + with pkgs; + lib.optionals stdenv.isLinux ( + [ + bash + binutils + coreutils + desktop-file-utils + diffutils + file + findutils + gawk + gdk-pixbuf + gdk-pixbuf.dev + glib + glib.dev + glibc.bin + gnugrep + gnused + gnutar + gzip + gtk3 + patchelf + pkg-config + squashfsTools + util-linux + which + xdg-utils + ] + ++ linuxTauriPackages + ); + linuxRuntimeClosure = if pkgs.stdenv.isLinux then pkgs.closureInfo { @@ -246,6 +283,10 @@ aarch64 = "sha256-Ak4f3LJchgv9hSN5I6lO0VubrAwCqQ/xCpCcsIdmxfU="; x86_64 = "sha256-Egjmp7HiZG4/sAbeqQC3K9hI7IYyS5l5t8lD8hHGacg="; }; + appimageRuntime = { + aarch64 = "sha256-fyeowVvyCi5GNC6kqXcEemnYtNZKEj/gteI7IP0pDIU="; + x86_64 = "sha256-okGdzkdWg5WuecAf+ppaNB3TOVgTUv8QTQc1J1Qxd+U="; + }; gtkPlugin = "sha256-yzefmwcz6a2fi9ePjC+gOK7yR4Uju31MjmT/ah6jUBo="; gstreamerPlugin = "sha256-wQe0nYTtv/xqsibtEAfgYmpPeqLDo2t3gr72I1HUnpQ="; }; @@ -267,6 +308,10 @@ url = "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-${arch}.AppImage"; hash = linuxTauriToolHashes.appimagePlugin.${arch}; }; + appimageRuntime = pkgs.fetchurl { + url = "https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-${arch}"; + hash = linuxTauriToolHashes.appimageRuntime.${arch}; + }; gtkPlugin = pkgs.fetchurl { url = "https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"; hash = linuxTauriToolHashes.gtkPlugin; @@ -275,12 +320,161 @@ url = "https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh"; hash = linuxTauriToolHashes.gstreamerPlugin; }; + linuxdeployWrapperSource = pkgs.writeText "maple-linuxdeploy-wrapper.c" '' + #include + #include + #include + #include + #include + + #ifndef LINUXDEPLOY_ARCH + #error "LINUXDEPLOY_ARCH is required" + #endif + + static char *wrapper_dir(const char *argv0) { + const char *slash = strrchr(argv0, '/'); + + if (slash == NULL) { + char *cwd = getcwd(NULL, 0); + if (cwd == NULL) { + perror("getcwd"); + } + return cwd; + } + + size_t len = (size_t)(slash - argv0); + if (len == 0) { + len = 1; + } + + char *dir = malloc(len + 1); + if (dir == NULL) { + perror("malloc"); + return NULL; + } + + memcpy(dir, argv0, len); + dir[len] = '\0'; + return dir; + } + + int main(int argc, char **argv) { + char *dir = wrapper_dir(argv[0]); + if (dir == NULL) { + return 127; + } + + char app_dir[8192]; + int written = snprintf( + app_dir, + sizeof(app_dir), + "%s/linuxdeploy-%s.AppDir", + dir, + LINUXDEPLOY_ARCH + ); + + if (written < 0 || (size_t)written >= sizeof(app_dir)) { + free(dir); + fprintf(stderr, "linuxdeploy AppDir path is too long\n"); + return 127; + } + + char app_run[8192]; + written = snprintf(app_run, sizeof(app_run), "%s/AppRun", app_dir); + if (written < 0 || (size_t)written >= sizeof(app_run)) { + free(dir); + fprintf(stderr, "linuxdeploy AppRun path is too long\n"); + return 127; + } + + char plugin_path[16384]; + char plugin_bin[8192]; + char support_bin[8192]; + written = snprintf( + plugin_bin, + sizeof(plugin_bin), + "%s/maple-linuxdeploy-tools/plugins", + dir + ); + if (written < 0 || (size_t)written >= sizeof(plugin_bin)) { + free(dir); + fprintf(stderr, "linuxdeploy plugin bin path is too long\n"); + return 127; + } + + written = snprintf( + support_bin, + sizeof(support_bin), + "%s/maple-linuxdeploy-tools/bin", + dir + ); + if (written < 0 || (size_t)written >= sizeof(support_bin)) { + free(dir); + fprintf(stderr, "linuxdeploy support bin path is too long\n"); + return 127; + } + + written = snprintf( + plugin_path, + sizeof(plugin_path), + "%s:%s", + plugin_bin, + support_bin + ); + if (written < 0 || (size_t)written >= sizeof(plugin_path)) { + free(dir); + fprintf(stderr, "linuxdeploy PATH is too long\n"); + return 127; + } + + free(dir); + + if (setenv("APPDIR", app_dir, 1) != 0) { + perror("setenv APPDIR"); + return 127; + } + + if (setenv("PATH", plugin_path, 1) != 0) { + perror("setenv PATH"); + return 127; + } + + unsetenv("APPIMAGE"); + unsetenv("APPIMAGE_EXTRACT_AND_RUN"); + unsetenv("ARGV0"); + + char **args = calloc((size_t)argc + 1, sizeof(char *)); + if (args == NULL) { + perror("calloc"); + return 127; + } + + int out = 0; + args[out++] = app_run; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--appimage-extract-and-run") == 0) { + continue; + } + args[out++] = argv[i]; + } + args[out] = NULL; + + execv(app_run, args); + fprintf(stderr, "failed to exec %s: %s\n", app_run, strerror(errno)); + return 127; + } + ''; in pkgs.runCommand "maple-tauri-linuxdeploy-tools-${arch}" { } '' mkdir -p "$out" + ${pkgs.stdenv.cc}/bin/cc -O2 -Wall -Wextra \ + -DLINUXDEPLOY_ARCH='"${linuxdeployArch}"' \ + ${linuxdeployWrapperSource} \ + -o "$out/linuxdeploy-${linuxdeployArch}.wrapper" install -m 0755 ${appRun} "$out/AppRun-${arch}" install -m 0755 ${linuxdeploy} "$out/linuxdeploy-${linuxdeployArch}.AppImage" install -m 0755 ${appimagePlugin} "$out/linuxdeploy-plugin-appimage.real.AppImage" + install -m 0755 ${appimageRuntime} "$out/appimage-runtime-${arch}" install -m 0755 ${gtkPlugin} "$out/linuxdeploy-plugin-gtk.sh" install -m 0755 ${gstreamerPlugin} "$out/linuxdeploy-plugin-gstreamer.sh" '' @@ -340,6 +534,7 @@ export MAPLE_NIX_GLIB_SCHEMAS=${pkgs.glib.dev}/share/glib-2.0/schemas export MAPLE_NIX_GTK_LIB=${pkgs.gtk3}/lib export MAPLE_NIX_LINUX_CLOSURE_INFO=${linuxRuntimeClosure} + export MAPLE_NIX_LINUXDEPLOY_SUPPORT_PATH=${lib.makeBinPath linuxdeploySupportPackages} ${lib.optionalString (tauriLinuxdeployTools != null) "export MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS=${tauriLinuxdeployTools}"} ${lib.optionalString (linuxTauriToolsArch != null) "export MAPLE_NIX_TAURI_LINUXDEPLOY_ARCH=${linuxTauriToolsArch}"} export GSTREAMER_PLUGINS_DIR=${gstreamerPlugins}/lib/gstreamer-1.0 diff --git a/scripts/ci/_common.sh b/scripts/ci/_common.sh index a1d8a3c6a..b99ff0f0a 100755 --- a/scripts/ci/_common.sh +++ b/scripts/ci/_common.sh @@ -199,10 +199,38 @@ append_env_word_once() { esac } +append_env_words_once() { + local var_name="$1" + local words="$2" + local current + + if [ -z "${words}" ]; then + return 0 + fi + + current="${!var_name:-}" + case " ${current} " in + *" ${words} "*) + ;; + *) + printf -v "${var_name}" '%s' "${current:+${current} }${words}" + export "${var_name}" + ;; + esac +} + append_rustflag_once() { local flag="$1" + local var_name append_env_word_once RUSTFLAGS "${flag}" + for var_name in \ + CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS \ + CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS; do + if [ -n "${!var_name:-}" ]; then + append_env_word_once "${var_name}" "${flag}" + fi + done } append_rust_remap_path_prefix() { @@ -287,7 +315,7 @@ generate_fake_tauri_updater_keypair() { ( cd "${FRONTEND_DIR}" - bun tauri signer generate \ + env -u LD_LIBRARY_PATH bun tauri signer generate \ --ci \ --password "${password}" \ --write-keys "${private_key}" \ @@ -303,7 +331,13 @@ generate_fake_tauri_updater_keypair() { } source_date_rfc3339() { - date -u -d "@${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH is required}" +"%Y-%m-%dT%H:%M:%SZ" + local epoch="${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH is required}" + + if date -u -d "@${epoch}" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then + return 0 + fi + + date -u -r "${epoch}" +"%Y-%m-%dT%H:%M:%SZ" } is_valid_xcode_developer_dir() { @@ -409,8 +443,10 @@ use_xcode_toolchain() { export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="${CC}" export CARGO_TARGET_AARCH64_APPLE_IOS_LINKER="${CC}" export CARGO_TARGET_AARCH64_APPLE_IOS_SIM_LINKER="${CC}" - export CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS="${CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS:+${CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS} }${macos_link_flags}" - export CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS="${CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS:+${CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS} }${macos_link_flags}" + append_env_words_once CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS "${RUSTFLAGS:-}" + append_env_words_once CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS "${RUSTFLAGS:-}" + append_env_words_once CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS "${macos_link_flags}" + append_env_words_once CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS "${macos_link_flags}" export CC_aarch64_apple_darwin="${CC}" export CXX_aarch64_apple_darwin="${CXX}" @@ -586,7 +622,7 @@ encode_base64_file_to_string() { repo_relative_path() { local path="$1" - printf '%s\n' "${path#${REPO_ROOT}/}" + printf '%s\n' "${path#"${REPO_ROOT}/"}" } print_file_hashes() { @@ -713,10 +749,38 @@ prepend_linux_runtime_library_path() { library_path="$(linux_runtime_library_path)" if [ -n "${library_path}" ]; then + if [ "${MAPLE_LINUX_RUNTIME_LIBRARY_PATH_ACTIVE:-0}" = "1" ]; then + return 0 + fi + + if [ "${LD_LIBRARY_PATH+x}" = "x" ]; then + export MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" + export MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH_SET=1 + else + unset MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH + export MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH_SET=0 + fi + export MAPLE_LINUX_RUNTIME_LIBRARY_PATH_ACTIVE=1 export LD_LIBRARY_PATH="${library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" fi } +restore_linux_runtime_library_path() { + if [ "${MAPLE_LINUX_RUNTIME_LIBRARY_PATH_ACTIVE:-0}" != "1" ]; then + return 0 + fi + + if [ "${MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH_SET:-0}" = "1" ]; then + export LD_LIBRARY_PATH="${MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH}" + else + unset LD_LIBRARY_PATH + fi + + unset MAPLE_LINUX_RUNTIME_LIBRARY_PATH_ACTIVE + unset MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH + unset MAPLE_LINUX_RUNTIME_ORIGINAL_LD_LIBRARY_PATH_SET +} + linuxdeploy_tools_arch() { case "$(uname -m)" in x86_64 | amd64) @@ -732,6 +796,174 @@ linuxdeploy_tools_arch() { esac } +write_chmod_patchelf_wrapper() { + local wrapper="$1" + local real_patchelf="$2" + local bash_path + + bash_path="/bin/bash" + cat > "${wrapper}" </dev/null || true + fi +done +exec "${real_patchelf}" "\$@" +EOF + chmod +x "${wrapper}" +} + +wrap_linuxdeploy_appdir_patchelf() { + local appdir_bin="$1" + local patchelf_path real_patchelf + + patchelf_path="${appdir_bin}/patchelf" + real_patchelf="${appdir_bin}/patchelf.real" + if [ ! -x "${patchelf_path}" ]; then + return 0 + fi + + mv "${patchelf_path}" "${real_patchelf}" + write_chmod_patchelf_wrapper "${patchelf_path}" "${real_patchelf}" +} + +prepare_linuxdeploy_support_bin() { + local support_bin="$1" + local bash_path path_dir entry name target real_pkg_config real_patchelf support_path + local -a path_dirs=() + + rm -rf "${support_bin}" + mkdir -p "${support_bin}" + + support_path="${MAPLE_NIX_LINUXDEPLOY_SUPPORT_PATH:-}" + if [ -z "${support_path}" ]; then + echo "MAPLE_NIX_LINUXDEPLOY_SUPPORT_PATH is required for reproducible linuxdeploy support tooling." >&2 + return 1 + fi + + # This path is exported by flake.nix and contains only pinned Nix package bins. + IFS=':' read -r -a path_dirs <<< "${support_path}" + for path_dir in "${path_dirs[@]}"; do + if [ -z "${path_dir}" ] || [ ! -d "${path_dir}" ] || [ "${path_dir}" = "${support_bin}" ]; then + continue + fi + + while IFS= read -r -d '' entry; do + name="$(basename "${entry}")" + case "${name}" in + linuxdeploy-plugin-*) + continue + ;; + esac + + if [ -e "${support_bin}/${name}" ] || [ -L "${support_bin}/${name}" ]; then + continue + fi + + target="$(readlink -f "${entry}" 2>/dev/null || true)" + if [ -z "${target}" ] || [ ! -x "${target}" ]; then + continue + fi + + ln -s "${target}" "${support_bin}/${name}" 2>/dev/null || true + done < <(find "${path_dir}" -maxdepth 1 \( -type f -o -type l \) -perm /111 -print0 2>/dev/null | LC_ALL=C sort -z) + done + + bash_path="/bin/bash" + real_pkg_config="$(resolve_bwrap_visible_command pkg-config pkgconf || true)" + if [ -n "${real_pkg_config}" ]; then + rm -f "${support_bin}/pkgconf" "${support_bin}/pkg-config" + cat > "${support_bin}/pkgconf" < "${wrapper}" <&2 + exit 1 +fi + +exec "\${real_plugin}" "\$@" +EOF + chmod +x "${wrapper}" +} + prepare_tauri_linuxdeploy_tools_cache() { if [ "$(host_os)" != "linux" ]; then return 0 @@ -742,59 +974,129 @@ prepare_tauri_linuxdeploy_tools_cache() { return 1 fi - local arch linuxdeploy_arch tools cache bash_path linuxdeploy_wrapper appimage_wrapper linuxdeploy_appdir_bin + local arch linuxdeploy_arch tools cache internal linuxdeploy_wrapper appimage_wrapper linuxdeploy_appdir_bin plugin_bin runtime_file appimage_real_appimage appimage_appdir gtk_real_plugin gstreamer_real_plugin arch="$(linuxdeploy_tools_arch)" linuxdeploy_arch="${arch}" tools="${MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS}" cache="${TAURI_DIR}/target/.tauri" - bash_path="$(command -v bash)" + internal="${cache}/maple-linuxdeploy-tools" linuxdeploy_wrapper="${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" appimage_wrapper="${cache}/linuxdeploy-plugin-appimage.AppImage" linuxdeploy_appdir_bin="${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/usr/bin" - - mkdir -p "${cache}" + plugin_bin="${internal}/plugins" + runtime_file="${internal}/appimage-runtime-${arch}" + appimage_real_appimage="${internal}/linuxdeploy-plugin-appimage.real.AppImage" + appimage_appdir="${internal}/linuxdeploy-plugin-appimage.AppDir" + gtk_real_plugin="${internal}/linuxdeploy-plugin-gtk.real.sh" + gstreamer_real_plugin="${internal}/linuxdeploy-plugin-gstreamer.real.sh" + + mkdir -p "${cache}" "${internal}" + prepare_linuxdeploy_support_bin "${internal}/bin" + rm -rf "${plugin_bin}" + mkdir -p "${plugin_bin}" + rm -f "${cache}/linuxdeploy-plugin-appimage.real.AppImage" + rm -rf "${cache}/linuxdeploy-plugin-appimage.AppDir" install -m 0755 "${tools}/AppRun-${arch}" "${cache}/AppRun-${arch}" + install -m 0755 "${tools}/linuxdeploy-${linuxdeploy_arch}.wrapper" "${linuxdeploy_wrapper}" install -m 0755 "${tools}/linuxdeploy-${linuxdeploy_arch}.AppImage" "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" - install -m 0755 "${tools}/linuxdeploy-plugin-appimage.real.AppImage" "${cache}/linuxdeploy-plugin-appimage.real.AppImage" - install -m 0755 "${tools}/linuxdeploy-plugin-gtk.sh" "${cache}/linuxdeploy-plugin-gtk.sh" - install -m 0755 "${tools}/linuxdeploy-plugin-gstreamer.sh" "${cache}/linuxdeploy-plugin-gstreamer.sh" + install -m 0755 "${tools}/linuxdeploy-plugin-appimage.real.AppImage" "${appimage_real_appimage}" + install -m 0755 "${tools}/appimage-runtime-${arch}" "${runtime_file}" + install -m 0755 "${tools}/linuxdeploy-plugin-gtk.sh" "${gtk_real_plugin}" + install -m 0755 "${tools}/linuxdeploy-plugin-gstreamer.sh" "${gstreamer_real_plugin}" extract_appimage_tool "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" "${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir" - extract_appimage_tool "${cache}/linuxdeploy-plugin-appimage.real.AppImage" "${cache}/linuxdeploy-plugin-appimage.AppDir" + extract_appimage_tool "${appimage_real_appimage}" "${appimage_appdir}" + wrap_linuxdeploy_appdir_patchelf "${linuxdeploy_appdir_bin}" - install -m 0755 "${cache}/linuxdeploy-plugin-gtk.sh" "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-gtk" - install -m 0755 "${cache}/linuxdeploy-plugin-gstreamer.sh" "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-gstreamer" + rm -f "${linuxdeploy_appdir_bin}"/linuxdeploy-plugin-* + write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gtk.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gtk.real.sh" + write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gstreamer.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gstreamer.real.sh" + write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gtk" "../linuxdeploy-plugin-gtk.real.sh" + write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gstreamer" "../linuxdeploy-plugin-gstreamer.real.sh" - cat > "${linuxdeploy_wrapper}" < "${appimage_wrapper}" <&2 - exit 1 -fi +stage_appdir_for_appimage() { + local mode="\${MAPLE_STAGE_APPDIR_FOR_APPIMAGE:-auto}" + local root="\$1" -args=() -for arg in "\$@"; do - case "\${arg}" in - --appimage-extract-and-run) + case "\${mode}" in + 1 | true | yes) + return 0 + ;; + 0 | false | no) + return 1 + ;; + auto) + case "\$(uname -s):\${root}" in + Linux:/Users/*) + return 0 + ;; + *) + return 1 + ;; + esac ;; *) - args+=("\${arg}") + echo "Unsupported MAPLE_STAGE_APPDIR_FOR_APPIMAGE=\${mode}; expected auto, 1, or 0." >&2 + return 1 ;; esac -done +} -exec "\${app_run}" "\${args[@]}" -EOF - chmod +x "${linuxdeploy_wrapper}" +run_appimage_plugin() { + local tmp_dir="" tmp_appdir="" output_parent="" status file previous arg + local -a plugin_args=() - cat > "${appimage_wrapper}" <&2 exit 1 fi -exec "\${real_plugin}" "\$@" +run_appimage_plugin "\$@" EOF chmod +x "${appimage_wrapper}" - rm -f "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-appimage" - cat > "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-appimage" < "${plugin_bin}/linuxdeploy-plugin-appimage" <&2 + return 1 + ;; + esac +} + +run_appimage_plugin() { + local tmp_dir="" tmp_appdir="" output_parent="" status file previous arg + local -a plugin_args=() + + if [ -z "\${appdir}" ] || ! stage_appdir_for_appimage "\${appdir}"; then + exec "\${real_plugin}" "\$@" + fi + + output_parent="\$(CDPATH= cd -- "\$(dirname -- "\${appdir}")" && pwd)" + tmp_dir="\$(mktemp -d)" + trap 'rm -rf "\${tmp_dir}"' EXIT + tmp_appdir="\${tmp_dir}/\$(basename -- "\${appdir}")" + cp -a --no-preserve=xattr "\${appdir}" "\${tmp_appdir}" + + previous="" + for arg in "\$@"; do + if [ "\${previous}" = "--appdir" ]; then + plugin_args+=("\${tmp_appdir}") + previous="" + continue + fi + + case "\${arg}" in + --appdir=*) + plugin_args+=("--appdir=\${tmp_appdir}") + ;; + --appdir) + plugin_args+=("--appdir") + previous="--appdir" + ;; + *) + plugin_args+=("\${arg}") + ;; + esac + done + + set +e + "\${real_plugin}" "\${plugin_args[@]}" + status="\$?" + set -e + + if [ "\${status}" -eq 0 ]; then + while IFS= read -r -d '' file; do + mv -f "\${file}" "\${output_parent}/\$(basename -- "\${file}")" + done < <(find "\${tmp_dir}" -maxdepth 1 -type f -name '*.AppImage' -print0) + fi + + rm -rf "\${tmp_dir}" + trap - EXIT + exit "\${status}" +} + for arg in "\$@"; do case "\${arg}" in --plugin-type) @@ -885,32 +1268,71 @@ if [ -n "\${appdir}" ]; then rm -f "\${appdir}/.DirIcon" fi -real_plugin="$(printf '%q' "${cache}/linuxdeploy-plugin-appimage.AppDir/AppRun")" +script_dir="\$(CDPATH= cd -- "\$(dirname -- "\$0")" && pwd)" +real_plugin="\${script_dir}/../linuxdeploy-plugin-appimage.AppDir/AppRun" +export LDAI_RUNTIME_FILE="\${script_dir}/../appimage-runtime-${arch}" if [ ! -x "\${real_plugin}" ]; then echo "Missing extracted linuxdeploy AppImage plugin at \${real_plugin}" >&2 exit 1 fi -exec "\${real_plugin}" "\$@" +run_appimage_plugin "\$@" EOF - chmod +x "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-appimage" + chmod +x "${plugin_bin}/linuxdeploy-plugin-appimage" print_file_hashes \ "${cache}/AppRun-${arch}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/AppRun" \ - "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-appimage" \ - "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-gtk" \ - "${linuxdeploy_appdir_bin}/linuxdeploy-plugin-gstreamer" \ - "${cache}/linuxdeploy-plugin-appimage.real.AppImage" \ + "${plugin_bin}/linuxdeploy-plugin-appimage" \ + "${plugin_bin}/linuxdeploy-plugin-gtk" \ + "${plugin_bin}/linuxdeploy-plugin-gstreamer" \ + "${appimage_real_appimage}" \ "${cache}/linuxdeploy-plugin-appimage.AppImage" \ - "${cache}/linuxdeploy-plugin-appimage.AppDir/AppRun" \ + "${appimage_appdir}/AppRun" \ + "${runtime_file}" \ "${cache}/linuxdeploy-plugin-gtk.sh" \ "${cache}/linuxdeploy-plugin-gstreamer.sh" } +verify_linuxdeploy_plugin_metadata() { + if [ "$(host_os)" != "linux" ]; then + return 0 + fi + + local plugin api_version plugin_type + for plugin in "${TAURI_DIR}/target/.tauri/maple-linuxdeploy-tools/plugins"/linuxdeploy-plugin-*; do + if [ ! -e "${plugin}" ]; then + continue + fi + if [ ! -x "${plugin}" ]; then + echo "linuxdeploy plugin is not executable: $(repo_relative_path "${plugin}")" >&2 + return 1 + fi + + api_version="$(run_with_nix_usr_bin "${plugin}" --plugin-api-version)" + plugin_type="$(run_with_nix_usr_bin "${plugin}" --plugin-type)" + + if [ "${api_version}" != "0" ]; then + echo "linuxdeploy plugin reported unsupported API ${api_version}: $(repo_relative_path "${plugin}")" >&2 + return 1 + fi + + case "${plugin_type}" in + input | output) + ;; + *) + echo "linuxdeploy plugin reported unsupported type ${plugin_type}: $(repo_relative_path "${plugin}")" >&2 + return 1 + ;; + esac + + printf 'verified-linuxdeploy-plugin type=%s api=%s %s\n' "${plugin_type}" "${api_version}" "$(repo_relative_path "${plugin}")" + done +} + extract_appimage_tool() { local appimage="$1" local out="$2" @@ -921,8 +1343,7 @@ extract_appimage_tool() { return 1 } - offset="$(appimage_squashfs_offset "${appimage}")" - if [ -z "${offset}" ]; then + if ! offset="$(appimage_squashfs_offset "${appimage}")"; then echo "Could not locate embedded SquashFS payload in AppImage: ${appimage}" >&2 return 1 fi @@ -1311,7 +1732,7 @@ verify_tauri_updater_signature() { tauri_updater_public_key_file "${pubkey}" decode_base64_file_to_file "${signature}" "${decoded_signature}" - if ! minisign -Vm "${artifact}" -p "${pubkey}" -x "${decoded_signature}" -q; then + if ! env -u LD_LIBRARY_PATH minisign -Vm "${artifact}" -p "${pubkey}" -x "${decoded_signature}" -q; then rm -rf "${tmp}" return 1 fi @@ -1337,7 +1758,7 @@ sign_tauri_updater_artifact() { ( cd "${FRONTEND_DIR}" - bun tauri signer sign "${artifact}" >/dev/null + env -u LD_LIBRARY_PATH bun tauri signer sign "${artifact}" >/dev/null ) } @@ -1406,6 +1827,33 @@ resolve_bwrap_visible_command() { return 1 } +install_bwrap_command_link() { + local dest_dir="$1" + local name="$2" + local source_path="$3" + local resolved + + if [ -z "${source_path}" ]; then + return 0 + fi + + resolved="$(readlink -f "${source_path}" 2>/dev/null || printf '%s\n' "${source_path}")" + if [ ! -x "${resolved}" ]; then + return 0 + fi + + rm -f "${dest_dir}/${name}" + case "${resolved}" in + /bin/* | /usr/*) + cp -L "${resolved}" "${dest_dir}/${name}" + chmod +x "${dest_dir}/${name}" + ;; + *) + ln -s "${resolved}" "${dest_dir}/${name}" + ;; + esac +} + run_with_nix_usr_bin() { if [ "$(host_os)" != "linux" ]; then "$@" @@ -1423,7 +1871,7 @@ run_with_nix_usr_bin() { } local bash_path bin_dir tool_bin usr_root tool tool_path - bash_path="$(command -v bash)" + bash_path="/bin/bash" bin_dir="$(mktemp -d)" tool_bin="$(mktemp -d)" usr_root="$(mktemp -d)" @@ -1431,16 +1879,13 @@ run_with_nix_usr_bin() { for tool in bash sh; do tool_path="$(command -v "${tool}" 2>/dev/null || true)" - if [ -n "${tool_path}" ]; then - ln -s "${tool_path}" "${bin_dir}/${tool}" - fi + install_bwrap_command_link "${bin_dir}" "${tool}" "${tool_path}" + install_bwrap_command_link "${usr_root}/bin" "${tool}" "${tool_path}" done for tool in env xdg-mime xdg-open update-desktop-database; do tool_path="$(command -v "${tool}" 2>/dev/null || true)" - if [ -n "${tool_path}" ]; then - ln -s "${tool_path}" "${usr_root}/bin/${tool}" - fi + install_bwrap_command_link "${usr_root}/bin" "${tool}" "${tool_path}" done local paths_file="${MAPLE_NIX_LINUX_CLOSURE_INFO:-}/store-paths" @@ -1845,12 +2290,13 @@ rebuild_rpm_package_from_payload() { local rpm="$1" local payload="$2" local topdir spec built_rpm - local version release arch + local version release arch rpm_build_shell command -v rpmbuild >/dev/null 2>&1 || { echo "rpmbuild is required to normalize RPM packages. Run through the flake CI shell." >&2 return 1 } + rpm_build_shell="$(command -v bash)" version="$(jq -r '.version' "${TAURI_DIR}/tauri.conf.json")" release="$(jq -r '.bundle.linux.rpm.release // "1"' "${TAURI_DIR}/tauri.conf.json")" @@ -1859,7 +2305,7 @@ rebuild_rpm_package_from_payload() { spec="${topdir}/SPECS/maple.spec" touch_tree_to_source_date_epoch "${payload}" - mkdir -p "${topdir}/BUILD" "${topdir}/BUILDROOT" "${topdir}/RPMS" "${topdir}/SOURCES" "${topdir}/SPECS" "${topdir}/SRPMS" + mkdir -p "${topdir}/BUILD" "${topdir}/BUILDROOT" "${topdir}/RPMS" "${topdir}/SOURCES" "${topdir}/SPECS" "${topdir}/SRPMS" "${topdir}/tmp" cat > "${spec}" <> "${spec}" - SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH}" rpmbuild -bb "${spec}" \ + env -u LD_LIBRARY_PATH SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH}" rpmbuild -bb "${spec}" \ --define "_topdir ${topdir}" \ + --define "_tmppath ${topdir}/tmp" \ + --define "_buildshell ${rpm_build_shell}" \ --define "_dbpath ${topdir}/rpmdb" \ --define "_buildhost (none)" \ --define "use_source_date_epoch_as_buildtime 1" \ @@ -1961,7 +2409,8 @@ linux_tauri_pr_config() { }, bundle: { createUpdaterArtifacts: false, - targets: ["deb", "rpm"], + useLocalToolsDir: true, + targets: ["appimage", "deb", "rpm"], linux: { appimage: { bundleMediaFramework: true, diff --git a/scripts/ci/android-pr.sh b/scripts/ci/android-pr.sh index ff6d79d2f..c8e58d34e 100755 --- a/scripts/ci/android-pr.sh +++ b/scripts/ci/android-pr.sh @@ -44,7 +44,15 @@ use_pr_environment configure_reproducible_build_metadata build_frontend_dist -toolchain_prebuilt="$(find "${NDK_HOME}/toolchains/llvm/prebuilt" -mindepth 1 -maxdepth 1 -type d | sort | head -n 1)" +toolchain_prebuilt="$( + find "${NDK_HOME}/toolchains/llvm/prebuilt" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \ + | LC_ALL=C sort \ + | head -n 1 || true +)" +if [ -z "${toolchain_prebuilt}" ] || [ ! -d "${toolchain_prebuilt}/bin" ]; then + echo "Could not find Android NDK LLVM prebuilt toolchain under NDK_HOME=${NDK_HOME}" >&2 + exit 1 +fi export PATH="${toolchain_prebuilt}/bin:${PATH}" tmp_toolchain_bin="$(mktemp -d)" diff --git a/scripts/ci/android-release.sh b/scripts/ci/android-release.sh index 02835871d..ea282498d 100755 --- a/scripts/ci/android-release.sh +++ b/scripts/ci/android-release.sh @@ -120,7 +120,15 @@ esac configure_reproducible_build_metadata build_frontend_dist -toolchain_prebuilt="$(find "${NDK_HOME}/toolchains/llvm/prebuilt" -mindepth 1 -maxdepth 1 -type d | sort | head -n 1)" +toolchain_prebuilt="$( + find "${NDK_HOME}/toolchains/llvm/prebuilt" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \ + | LC_ALL=C sort \ + | head -n 1 || true +)" +if [ -z "${toolchain_prebuilt}" ] || [ ! -d "${toolchain_prebuilt}/bin" ]; then + echo "Could not find Android NDK LLVM prebuilt toolchain under NDK_HOME=${NDK_HOME}" >&2 + exit 1 +fi export PATH="${toolchain_prebuilt}/bin:${PATH}" tmp_toolchain_bin="$(mktemp -d)" diff --git a/scripts/ci/desktop-pr.sh b/scripts/ci/desktop-pr.sh index 03e621fc6..757022bf2 100755 --- a/scripts/ci/desktop-pr.sh +++ b/scripts/ci/desktop-pr.sh @@ -19,14 +19,22 @@ case "$(host_os)" in prepare_linux_onnxruntime export APPIMAGE_EXTRACT_AND_RUN="${APPIMAGE_EXTRACT_AND_RUN:-1}" export NO_STRIP="${NO_STRIP:-true}" + prepend_linux_runtime_library_path + prepare_tauri_linuxdeploy_tools_cache + verify_linuxdeploy_plugin_metadata + run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins run_with_nix_usr_bin pkg-config --modversion glib-2.0 - bun tauri build --verbose --no-sign --config "$(linux_tauri_pr_config)" + remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" + remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" + remove_build_tree "${TAURI_DIR}/target/release/bundle/rpm" + run_with_nix_usr_bin bun tauri build --verbose --no-sign --config "$(linux_tauri_pr_config)" + restore_linux_runtime_library_path normalize_linux_desktop_packages desktop_artifacts=() while IFS= read -r -d '' file; do desktop_artifacts+=("${file}") - done < <(find "${TAURI_DIR}/target/release/bundle" -type f \( -name '*.deb' -o -name '*.rpm' \) -print0 | LC_ALL=C sort -z) + done < <(find "${TAURI_DIR}/target/release/bundle" -type f \( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \) -print0 | LC_ALL=C sort -z) repro_dir="${TAURI_DIR}/target/reproducibility" write_sha256_manifest "${repro_dir}/desktop-pr-linux-final.sha256" "${desktop_artifacts[@]}" "${TAURI_DIR}/target/release/maple" print_file_hashes "${desktop_artifacts[@]}" @@ -43,7 +51,7 @@ case "$(host_os)" in fake_signature_artifacts=() while IFS= read -r -d '' file; do fake_signature_artifacts+=("${file}") - done < <(find "${TAURI_DIR}/target/release/bundle" -type f \( -name '*.deb.sig' -o -name '*.rpm.sig' \) -print0 | LC_ALL=C sort -z) + done < <(find "${TAURI_DIR}/target/release/bundle" -type f \( -name '*.AppImage.sig' -o -name '*.deb.sig' -o -name '*.rpm.sig' \) -print0 | LC_ALL=C sort -z) if [ "${#fake_signature_artifacts[@]}" -eq 0 ]; then echo "No fake Linux updater signature artifacts were created." >&2 diff --git a/scripts/ci/desktop-release.sh b/scripts/ci/desktop-release.sh index fa736fbf9..21a9de643 100755 --- a/scripts/ci/desktop-release.sh +++ b/scripts/ci/desktop-release.sh @@ -39,6 +39,8 @@ case "$(host_os)" in export NO_STRIP="${NO_STRIP:-true}" prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache + verify_linuxdeploy_plugin_metadata + run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" @@ -48,6 +50,7 @@ case "$(host_os)" in run_with_nix_usr_bin pkg-config --modversion glib-2.0 (cd "${TAURI_DIR}" && cargo build --bins --features tauri/custom-protocol --release) run_with_nix_usr_bin bun tauri build --verbose --config "${release_config}" + restore_linux_runtime_library_path normalize_linux_desktop_packages normalized_linux_packages=() diff --git a/scripts/ci/ios-pr.sh b/scripts/ci/ios-pr.sh index 3225ea4f1..f432dc1a8 100755 --- a/scripts/ci/ios-pr.sh +++ b/scripts/ci/ios-pr.sh @@ -30,15 +30,21 @@ configure_reproducible_build_metadata build_frontend_dist ios_project_state_dir="" +ios_info_plist_present=0 +ios_entitlements_present=0 restore_ios_build_state() { remove_generated_ios_cargo_config if [ -n "${ios_project_state_dir}" ] && [ -d "${ios_project_state_dir}" ]; then if [ -f "${ios_project_state_dir}/Info.plist" ]; then cp "${ios_project_state_dir}/Info.plist" "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" + elif [ "${ios_info_plist_present}" = "0" ]; then + rm -f "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" fi if [ -f "${ios_project_state_dir}/maple_iOS.entitlements" ]; then cp "${ios_project_state_dir}/maple_iOS.entitlements" "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" + elif [ "${ios_entitlements_present}" = "0" ]; then + rm -f "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" fi rm -rf "${ios_project_state_dir}" fi @@ -46,9 +52,15 @@ restore_ios_build_state() { cd "${TAURI_DIR}" ios_project_state_dir="$(mktemp -d)" -cp "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" "${ios_project_state_dir}/Info.plist" -cp "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" "${ios_project_state_dir}/maple_iOS.entitlements" trap restore_ios_build_state EXIT +if [ -f "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" ]; then + ios_info_plist_present=1 + cp "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" "${ios_project_state_dir}/Info.plist" +fi +if [ -f "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" ]; then + ios_entitlements_present=1 + cp "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" "${ios_project_state_dir}/maple_iOS.entitlements" +fi verify_ios_onnxruntime_manifest print_ios_onnxruntime_hashes diff --git a/scripts/ci/ios-release.sh b/scripts/ci/ios-release.sh index 5b56f723d..466ea0d6d 100755 --- a/scripts/ci/ios-release.sh +++ b/scripts/ci/ios-release.sh @@ -35,15 +35,21 @@ if [ -z "${APPLE_API_ISSUER:-}" ] || [ -z "${APPLE_API_KEY:-}" ] || [ -z "${APPL fi ios_project_state_dir="" +ios_info_plist_present=0 +ios_entitlements_present=0 restore_ios_build_state() { remove_generated_ios_cargo_config if [ -n "${ios_project_state_dir}" ] && [ -d "${ios_project_state_dir}" ]; then if [ -f "${ios_project_state_dir}/Info.plist" ]; then cp "${ios_project_state_dir}/Info.plist" "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" + elif [ "${ios_info_plist_present}" = "0" ]; then + rm -f "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" fi if [ -f "${ios_project_state_dir}/maple_iOS.entitlements" ]; then cp "${ios_project_state_dir}/maple_iOS.entitlements" "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" + elif [ "${ios_entitlements_present}" = "0" ]; then + rm -f "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" fi rm -rf "${ios_project_state_dir}" fi @@ -51,9 +57,15 @@ restore_ios_build_state() { cd "${TAURI_DIR}" ios_project_state_dir="$(mktemp -d)" -cp "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" "${ios_project_state_dir}/Info.plist" -cp "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" "${ios_project_state_dir}/maple_iOS.entitlements" trap restore_ios_build_state EXIT +if [ -f "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" ]; then + ios_info_plist_present=1 + cp "${TAURI_DIR}/gen/apple/maple_iOS/Info.plist" "${ios_project_state_dir}/Info.plist" +fi +if [ -f "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" ]; then + ios_entitlements_present=1 + cp "${TAURI_DIR}/gen/apple/maple_iOS/maple_iOS.entitlements" "${ios_project_state_dir}/maple_iOS.entitlements" +fi repro_dir="${TAURI_DIR}/target/reproducibility" mkdir -p "${repro_dir}" diff --git a/scripts/ci/latest-json.sh b/scripts/ci/latest-json.sh index 47af5ca69..c09166ea9 100755 --- a/scripts/ci/latest-json.sh +++ b/scripts/ci/latest-json.sh @@ -20,6 +20,7 @@ if [ ! -d "${artifacts_dir}" ]; then fi configure_reproducible_build_metadata +pub_date="${MAPLE_LATEST_JSON_PUB_DATE:-$(source_date_rfc3339)}" find_one_artifact() { local pattern="$1" @@ -49,7 +50,7 @@ tmp="$(mktemp)" jq -S -n \ --arg version "${release_tag#v}" \ --arg notes "See the release notes at https://github.com/OpenSecretCloud/Maple/releases/tag/${release_tag}" \ - --arg pub_date "$(source_date_rfc3339)" \ + --arg pub_date "${pub_date}" \ --arg macos_sig "${macos_sig_content}" \ --arg linux_sig "${linux_sig_content}" \ --arg macos_url "${macos_url}" \ diff --git a/scripts/ci/rust.sh b/scripts/ci/rust.sh index 16dc7816a..6d6565ed4 100755 --- a/scripts/ci/rust.sh +++ b/scripts/ci/rust.sh @@ -9,4 +9,4 @@ configure_sccache prepare_linux_onnxruntime cd "${TAURI_DIR}" -cargo test --all-targets +cargo test --all-targets --locked diff --git a/scripts/ci/verify-release-artifacts.sh b/scripts/ci/verify-release-artifacts.sh index 3586e7477..958c79ac2 100755 --- a/scripts/ci/verify-release-artifacts.sh +++ b/scripts/ci/verify-release-artifacts.sh @@ -90,6 +90,11 @@ manifest_single_digest() { awk 'NF >= 3 && $1 ~ /^sha256-/ { print $2; exit }' "${manifest}" } +manifest_digests() { + local manifest="$1" + awk 'NF >= 3 && $1 ~ /^sha256-/ { print $2 }' "${manifest}" +} + verify_file_manifest() { local manifest="$1" local digest label file actual @@ -454,7 +459,7 @@ verify_macos() { verify_ios() { local final_manifest unsigned_manifest signed_manifest payload_manifest - local unsigned_digest signed_digest payload_digest + local unsigned_digest signed_digest payload_digest payload_seen payload_mismatch final_manifest="$(proof_file_required ios-release-final.sha256)" unsigned_manifest="$(proof_file_required ios-release-unsigned-app-canonical.sha256)" @@ -485,21 +490,29 @@ verify_ios() { fi verify_canonical_apple_manifest "${payload_manifest}" - payload_digest="$(manifest_single_digest "${payload_manifest}")" - if [ -z "${payload_digest}" ]; then + payload_seen=0 + payload_mismatch=0 + while IFS= read -r payload_digest; do + [ -n "${payload_digest}" ] || continue + payload_seen=1 + if [ "${payload_digest}" = "${signed_digest}" ]; then + printf 'verified-ios-exported-payload-proof %s\n' "${payload_digest}" + else + payload_mismatch=1 + echo "iOS IPA payload canonical proof does not match signed app proof." >&2 + echo "signed=${signed_digest:-missing}" >&2 + echo "payload=${payload_digest}" >&2 + fi + done < <(manifest_digests "${payload_manifest}") + if [ "${payload_seen}" -eq 0 ]; then echo "iOS IPA payload canonical proof is missing." >&2 return 1 fi - if [ "${payload_digest}" != "${signed_digest}" ]; then - echo "iOS IPA payload canonical proof does not match signed app proof." >&2 - echo "signed=${signed_digest:-missing}" >&2 - echo "payload=${payload_digest:-missing}" >&2 + if [ "${payload_mismatch}" -ne 0 ]; then if [ "${MAPLE_ENFORCE_IOS_SIGNED_REPRODUCIBILITY:-0}" = "1" ]; then return 1 fi - printf 'warning-ios-exported-payload-proof-mismatch signed=%s payload=%s\n' "${signed_digest}" "${payload_digest}" - else - printf 'verified-ios-exported-payload-proof %s\n' "${payload_digest}" + printf 'warning-ios-exported-payload-proof-mismatch signed=%s\n' "${signed_digest}" fi verify_ios_signatures }