pure-data/msw/build-nsi.sh

#!/bin/sh

# script to call makensis (NSIS installer generator, adapted for mingw32)
# to make a Pd install/uninstall executable for MS windows.
# slightly adapted from a script by Roman Haefeli

cat > /dev/null << .

*** The README adapted from Roman: ***

The included build.sh is a shell script to create a proper Windows
installer (.exe) out of a Pure Data build tree.

Features of the resulting installer:

 * Install Pure Data to user selectable destination
   (Default 64bit OS 64bit APP: "%Program Files%\Pd")
   (Default 64bit OS 32bit APP: "%Program Files (x86)%\Pd")
   (Default 32bit OS 32bit APP: "%Program Files%\Pd")

 * Create Startmenu entries (optional)

 * Set proper file associations in Windows registry so that .pd files
   are opened with Pure Data and have proper icons in Explorer (optional)

 * Include uninstaller to cleanly wipe Pure Data from Windows (including
   Startmenu and registry entries)

Prerequisites for building the installer
----------------------------------------

* makensis, from the package mingw32-nsis

How to build installer
----------------------

Run the script and specify the directory containing a windows build of
Pure Data:

 ./build-nsi.sh <path-to-pd-dir> <pd-version-number> <arch (32 or 64)>

The scripts dynamically creates a list of all files to be included in the
installer and then runs makensis. The result is an installer named
pd-<VERSION>.windows-installer.exe.  Various parameters can be set in the
file pd.nsi.
.

# Set some defaults
SCRIPTDIR=${0%/*}
OUTDIR=/tmp
WORKDIR=$(mktemp -d || echo /tmp)
INSTALLFILE="${WORKDIR}/install_files_list.nsh"
UNINSTALLFILE="${WORKDIR}/uninstall_files_list.nsh"
LICENSEFILE="${WORKDIR}/license_page.nsh"
NSIFILE="${WORKDIR}/pd.nsi"

error() {
  echo "$@" 1>&2
}

cleanup() {
 if [ "${WORKDIR}" != "/tmp" ]; then
    rm -rf "${WORKDIR}"
 fi
 exit "$1"
}

usage() {
  cat << EOF
Helper script to build a proper Windows installer out of a
Pd build tree.

Usage:
$0 [ OPTIONS ] <path_to_pd_build> [<version> [<wish-exec-name> [<arch>]]]

 Options:

  -o,--outdir DIR      Output directory (where the generated installer binary
                       can be found after a succesfull run)

  -h,--help            Print this help

 Arguments:

  <path_to_pd_build>   input directory (containing 'bin/pd.exe')
                       such as created by 'make app'
  <version>            Pd-version to create installer for (e.g. '0.32-8')
                       DEFAULT: autodetect from <path_to_pd_build>
  <wish-exec-name>     name of the Tcl/Tk interpreter executable
                       e.g., 'wish85.exe'
                       DEFAULT: use a "bin/wish*.exe" in <path_to_pd_build>
  <ARCH>               architecture of the Pd executable.
                       valid values are '32' for x86 and '64' for x86_64
                       DEFAULT: autodetect from "bin/pd.exe" in <path_to_pd_build>
EOF
  cleanup 1
}


# Parse command line arguments
#----------------------------------------------------------
while [ "$1" != "" ] ; do
    case $1 in
        -o|--outdir)
            if [  $# = 0 ]; then
                error "-o,--outdir requires a DIR argument"
                cleanup 1
            fi
            shift 1
            OUTDIR="$1"
            ;;
        -h|--help)
            usage
            ;;
        *)
            break ;;
    esac
    shift 1
done

# show usage when invoked without args
if [ $# -lt 1 ]
then
    usage
fi

PDWINDIR=$(realpath "$1")
PDVERSION="$2"
WISHNAME="$3"
PDARCH="$4"

if [ -z "${PDVERSION}" ]; then
    ver=${PDWINDIR##*/}
    if [ -n "${ver}" ] && [ "${ver}" != "${ver#*-}" ]; then
        PDVERSION="${ver#*-}"
    fi
fi

# Check validity of specified PDWINDIR
if ! [ -d "${PDWINDIR}" ]
then
  error "'${PDWINDIR}' is not a directory"
  cleanup 1
else
  for f in pd.exe pd.com pd64.exe pd64.com pd32.exe pd32.com pd.exe; do
    pd_exe="${PDWINDIR}/bin/${f}"
    [ -f "${pd_exe}" ] && break
  done
  if ! [ -f "${pd_exe}" ]; then
	  error "Could not find ${f}. Is this a Windows build?"
	  cleanup 1
  fi
fi

if [  -z "${PDVERSION}" ]; then
    error "could not auto-detect the version from ${PDWINDIR}"
    cleanup 1
fi

# autodetect wishname if none is given
if [ -z "${WISHNAME}" ]; then
	WISHNAME=$(find "${PDWINDIR}/bin" -iname "wish*.exe" -print -quit)

	if [ -n "${WISHNAME}" ]; then
		WISHNAME=$(basename "${WISHNAME}")
	fi
fi
if [ -z "${WISHNAME}" ]; then
    error "Unable to automatically determine <wish-exec-name>."
    cleanup 1
fi

# autodetect architecture if not given on the cmdline
if [  "${PDARCH}" = "" ]; then
    if file -b "${pd_exe}" | grep -E "^PE32 .* 80386 " >/dev/null; then
        PDARCH=32
    elif file -b "${pd_exe}" | grep -E "^PE32\+ .* x86-64 " >/dev/null; then
        PDARCH=64
    fi
fi
if [  "${PDARCH}" = "" ]; then
    error "Unable to automatically determine <arch>."
    cleanup 1
fi

OUTDIR=$(realpath "${OUTDIR}")

#### WRITE INSTALL LIST #########################################
find "${PDWINDIR}" \
  -mindepth 0 \
  -type d \
  -printf 'SetOutPath "$INSTDIR/%P"\n' \
  -exec \
    find {} \
    -maxdepth 1 \
    -type f \
    -printf 'File "%p"\n' \; \
    -not -name "*.o" \
  | sed 's|/|\\|g' \
  > "${INSTALLFILE}"

#### WRITE UNINSTALL FILE LIST ###################################
# clear
echo > "${UNINSTALLFILE}"

# delete files
find "${PDWINDIR}" -type f -printf 'Delete "$INSTDIR/%P"\n' \
  -not -name "*.o" \
  | sed 's|/|\\|g' \
  >> "${UNINSTALLFILE}"

# delete directories
# tac is used to reverse order so that dirs are removed in depth-first order
find "${PDWINDIR}" -type d -printf 'RMDir "$INSTDIR/%P"\n' \
  | tac \
  | sed 's|/|\\|g' \
  >> "${UNINSTALLFILE}"

#### WRITE LICENSE INCLUDE ######################################
echo "!insertmacro MUI_PAGE_LICENSE \"${PDWINDIR}/LICENSE.txt\"" \
  > "${LICENSEFILE}"

# if we are on mingw/cygwin, we have to unmangle the directories
WOUTDIR=$(cygpath -m "${OUTDIR}\\" || echo "${OUTDIR}/" | sed 's|/|\\\\|g')

# Outfile into pd.nsi script
cat "${SCRIPTDIR}/pd.nsi" | sed \
	-e "s|OutFile \"/tmp/|OutFile \"${WOUTDIR}|" \
	> "${NSIFILE}"

# check if we have nsis compiler
nsis_version=$(makensis -version)
nsis_exit=$?
case $nsis_exit in
  0) error "Found NSIS version $nsis_version"
     ;;
  1) error "makensis returned error. What's the problem?"
     cleanup 1
     ;;
  2) error "NSIS is not found.  install, e.g., mingw32-nsis."
     cleanup 1
     ;;
  *) error "Unknown error"
     cleanup 1
     ;;
esac

# copy installer custom artwork
cp "${SCRIPTDIR}/installer-art/big.bmp" "${WORKDIR}/big.bmp"
cp "${SCRIPTDIR}/installer-art/small.bmp" "${WORKDIR}/small.bmp"
cp "${SCRIPTDIR}/../tcl/pd.ico" "${WORKDIR}/pd.ico"

# run the build
mkdir -p "${OUTDIR}"
if makensis -DPDVER="${PDVERSION}" -DWISHN="${WISHNAME}" -DARCHI="${PDARCH}" \
    -DPDEXE=$(basename "${pd_exe}") "${NSIFILE}" 1>&2
then
  error "Build successful"
  echo "${OUTDIR}/pd-${PDVERSION}.windows-installer.exe"
else
  error "Some error occurred during compilation of ${NSIFILE}"
  error "(files are not cleaned up so you can inspect them)"
  exit 1
fi
cleanup 0