build-msi.sh 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
  5. DIST_DIR="${DIST_DIR:-${REPO_ROOT}/dist}"
  6. ARCH="${ARCH:-amd64}"
  7. ZIP_NAME="OliveTin-windows-${ARCH}.zip"
  8. ZIP_PATH="${DIST_DIR}/${ZIP_NAME}"
  9. MSI_NAME="OliveTin-windows-${ARCH}.msi"
  10. MSI_PATH="${DIST_DIR}/${MSI_NAME}"
  11. if [[ ! -f "${ZIP_PATH}" ]]; then
  12. echo "Windows archive not found: ${ZIP_PATH}" >&2
  13. exit 1
  14. fi
  15. if ! command -v wixl >/dev/null || ! command -v wixl-heat >/dev/null; then
  16. echo "wixl and wixl-heat are required (install the wixl/msitools package)" >&2
  17. exit 1
  18. fi
  19. normalize_msi_version() {
  20. local raw="${1#v}"
  21. raw="${raw%%-*}"
  22. if [[ ! "${raw}" =~ ^[0-9]+(\.[0-9]+){0,3}$ ]]; then
  23. echo "Invalid MSI version (expected major[.minor[.patch[.build]]]): ${1}" >&2
  24. return 1
  25. fi
  26. local -a parts=()
  27. IFS='.' read -r -a parts <<<"${raw}"
  28. local major="${parts[0]:-0}"
  29. local minor="${parts[1]:-0}"
  30. local patch="${parts[2]:-0}"
  31. printf '%s.%s.%s' "${major}" "${minor}" "${patch}"
  32. }
  33. VERSION="${VERSION:-}"
  34. if [[ -z "${VERSION}" ]]; then
  35. VERSION="$(git -C "${REPO_ROOT}" describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || true)"
  36. fi
  37. if [[ -z "${VERSION}" ]]; then
  38. echo "Could not determine release version; set VERSION explicitly" >&2
  39. exit 1
  40. fi
  41. MSI_VERSION="$(normalize_msi_version "${VERSION}")" || exit 1
  42. STAGING="$(mktemp -d)"
  43. APP_STAGING="$(mktemp -d)"
  44. HEAT_WXS="$(mktemp)"
  45. trap 'rm -rf "${STAGING}" "${APP_STAGING}" "${HEAT_WXS}"' EXIT
  46. unzip -q "${ZIP_PATH}" -d "${STAGING}"
  47. SOURCE_ROOT="${STAGING}/OliveTin-windows-${ARCH}"
  48. if [[ ! -f "${SOURCE_ROOT}/OliveTin.exe" ]]; then
  49. echo "OliveTin.exe not found in ${SOURCE_ROOT}" >&2
  50. exit 1
  51. fi
  52. if [[ ! -f "${SOURCE_ROOT}/config.yaml" ]]; then
  53. echo "config.yaml not found in ${SOURCE_ROOT}" >&2
  54. exit 1
  55. fi
  56. if ! command -v objdump >/dev/null; then
  57. echo "objdump is required to verify OliveTin.exe Windows resources" >&2
  58. exit 1
  59. fi
  60. if ! objdump -h "${SOURCE_ROOT}/OliveTin.exe" | grep -q '[[:space:]]\.rsrc[[:space:]]'; then
  61. echo "OliveTin.exe is missing embedded Windows resources (.rsrc); use main: . in .goreleaser.yml" >&2
  62. exit 1
  63. fi
  64. mkdir -p "${APP_STAGING}/webui"
  65. cp "${SOURCE_ROOT}/OliveTin.exe" "${APP_STAGING}/"
  66. cp -a "${SOURCE_ROOT}/webui/." "${APP_STAGING}/webui/"
  67. (
  68. cd "${APP_STAGING}"
  69. find . -type f | sed 's|^\./||'
  70. ) | wixl-heat \
  71. -p "" \
  72. --component-group CG.AppFiles \
  73. --var var.SourceDir \
  74. --directory-ref INSTALLDIR \
  75. --win64 \
  76. > "${HEAT_WXS}"
  77. wixl \
  78. -v \
  79. -a x64 \
  80. -D "Version=${MSI_VERSION}" \
  81. -D "Win64=yes" \
  82. -D "SourceDir=${APP_STAGING}" \
  83. -D "ConfigSource=${SOURCE_ROOT}/config.yaml" \
  84. -o "${MSI_PATH}" \
  85. "${SCRIPT_DIR}/OliveTin.wxs" \
  86. "${HEAT_WXS}"
  87. if ! msiinfo export "${MSI_PATH}" Media 2>/dev/null | grep -q '#cab1.cab'; then
  88. echo "MSI cabinet is not embedded (expected #cab1.cab in Media table); check EmbedCab in OliveTin.wxs" >&2
  89. exit 1
  90. fi
  91. echo "Built ${MSI_PATH}"