Create a MacOS X App Bundle Using Homebrew

From Octave
Jump to navigation Jump to search

Introduction

This tutorial explains how to build Octave.app (version >4) using Homebrew. It is recommended that users either

or use a package manager as described here. The former App bundle (version <=3.8) was created using Macports and called Octave from /usr/opt/local which is problematic with recent OS X (>=10.10). Therefore Octave.app is now installed in /Applications/Octave.app using homebrew. It does not interfere with local installations or package managers.

Prerequisites

  • Install Xcode via the Mac App Store.
  • Install the command line tools by xcode-select --install.
  • Optional: install XQuartz, e.g. if you want to use Gnuplot with X11 backend.

Creating the App Bundle and Disk Image

The script will ask some questions, e.g. regarding the installation path (/Applications/Octave.app is recommended) or whether Octave should be compiled with or without graphical user interface and finally whether a disk image should be created or not. If you choose to install to the Applications folder then the script needs to run with administration rights.

#!/bin/sh

# determine install dir
if [ "$1" != "defaults" ]; then
	read -p "In which directory do you want Octave to be installed? [/Applications/Octave.app]: " install_dir
fi
install_dir=${install_dir:-"/Applications/Octave.app"}

# check if 
if [ -d "$install_dir" ]; then
	echo "Directory $install_dir exists. Please remove it, e.g."
	echo "> rm -rf $install_dir"
	exit;
fi

# ask the user about feautures
if [ "$1" != "defaults" ]; then
	read -p "Do you want to build the GUI? [Y/n]: " build_gui
	read -p "Do you want to build a development snapshot [y/N]: " build_devel
	read -p "Do you want to create a DMG image? [y/N]: " build_dmg
	read -p "Where do you want to store the DMG? [$HOME]: " dmg_dir
fi

# set default values if nothing has been specified
build_gui=${build_gui:-y}
build_devel=${build_devel:-n}
build_dmg=${build_dmg:-y}
dmg_dir=${dmg_dir:-$HOME}

# create applescript for starting. The cli version starts a terminal session.
# A future applescript could check whether cl-tools are installed or set gnuterm settings
tmp_script=$(mktemp /tmp/octave-XXXX);
if [ "$build_gui" == "y" ]; then
cat >"$tmp_script" <<EOF
on open argv
path_check()
set filename to "'" & POSIX path of item 1 of argv & "'"
do shell script "export GNUTERM='qt';export PATH=/Applications/Octave.app/Contents/Resources/usr/bin/:\$PATH;cd ~;/Applications/Octave.app/Contents/Resources/usr/bin/octave --force-gui --persist --eval \"edit " & filename & "\" | logger 2>&1"
end open
on run
path_check()
do shell script "export GNUTERM='qt';export PATH=/Applications/Octave.app/Contents/Resources/usr/bin/:\$PATH;cd ~;/Applications/Octave.app/Contents/Resources/usr/bin/octave --force-gui  | logger 2>&1"
end run
on path_check()
if not (POSIX path of (path to me) contains "$install_dir") then
display dialog "Please move Octave to $install_dir" with icon stop with title "Error" buttons {"OK"}
error number -128
end if
end path_check
EOF
else
cat >"$tmp_script" <<EOF
on run
path_check()
tell application "Terminal"
do script "export GNUTERM='qt';export PATH=/Applications/Octave.app/Contents/Resources/usr/bin/:\$PATH;cd ~;clear;octave"
activate
end tell
end run
on path_check()
if not (POSIX path of (path to me) contains "$install_dir") then
display dialog "Please move Octave to $install_dir" with icon stop with title "Error" buttons {"OK"}
error number -128
end if
end path_check
EOF
fi

# create appbundle
osacompile -o"$install_dir" "$tmp_script"

# install brew
mkdir -p "$install_dir/Contents/Resources/usr"
curl -L https://github.com/Homebrew/homebrew/tarball/master | tar xz --strip 1 -C "$install_dir/Contents/Resources/usr"

# be conservative regarding architectures
sed -i '' 's/march=native/march=core2/g' "$install_dir/Contents/Resources/usr/Library/Homebrew/extend/ENV/super.rb" 
sed -i '' 's/march=native/march=core2/g' "$install_dir/Contents/Resources/usr/Library/Homebrew/extend/ENV/std.rb" 

# start compiling
cd "$install_dir/Contents/Resources/usr/bin"
./brew tap homebrew/science
./brew install imagemagick --with-librsvg
./brew install graphicsmagick --with-quantum-depth-16

# we prefer openblas over Apple's BLAS implementation
./brew install arpack --with-openblas
./brew install qrupdate --with-openblas
./brew install suite-sparse421 --with-openblas

# use github mirror to gnuplot 5.1 (devel)
./brew install gnuplot --with-qt --with-cairo --universal --verbose --HEAD

# get newest octave formula and then build octave
curl https://raw.githubusercontent.com/schoeps/homebrew-science/octave4r3/octave.rb -o "$install_dir/Contents/Resources/usr/Library/Taps/homebrew/homebrew-science/octave.rb"
octave_settings="--build-from-source --without-java --universal --with-audio --with-openblas"
if [ "$build_devel" == "y" ]; then
	octave_settings="$octave_settings --devel"
fi
if [ "$build_gui" == "y" ]; then
	octave_settings="$octave_settings --with-gui"
fi
./brew install octave $octave_settings

# get octave version
oct_ver="$(./octave --version |sed -n 1p |grep -o '\d\..*$' )"
oct_ver_string="$(./octave --version | sed -n 1p)"
oct_copy="$(./octave --version | sed -n 2p | cut -c 15- )"

# rebuilding fontconfig from source seems to fix gnuplot font problems
./brew uninstall fontconfig
./brew install fontconfig --build-from-source

# create a nice iconset (the octave icons in "share/icons" are not reliable)
svg_icon="$install_dir/Contents/Resources/usr/opt/octave/share/icons/hicolor/scalable/apps/octave.svg"
tmp_iconset="$(mktemp -d /tmp/iconset-XXXX)/droplet.iconset"
mkdir -p "$tmp_iconset"
./convert -background none -resize 16x16 "$svg_icon" "$tmp_iconset/icon_16x16.png"
./convert -background none -resize 32x32 "$svg_icon" "$tmp_iconset/icon_16x16@2x.png"
./convert -background none -resize 32x32 "$svg_icon" "$tmp_iconset/icon_32x32.png"
./convert -background none -resize 64x64 "$svg_icon" "$tmp_iconset/icon_32x32@2x.png"
./convert -background none -resize 128x128 "$svg_icon" "$tmp_iconset/icon_128x128.png"
./convert -background none -resize 256x256 "$svg_icon" "$tmp_iconset/icon_128x128@2x.png"
./convert -background none -resize 256x256 "$svg_icon" "$tmp_iconset/icon_256x256.png"
./convert -background none -resize 512x512 "$svg_icon" "$tmp_iconset/icon_256x256@2x.png"
./convert -background none -resize 512x512 "$svg_icon" "$tmp_iconset/icon_512x512.png"
./convert -background none -resize 1024x1024 "$svg_icon" "$tmp_iconset/icon_512x512@2x.png"
iconutil -c icns -o "$install_dir/Contents/Resources/octave.icns" "$tmp_iconset"

# modify some entries in the Application plist
defaults write "$install_dir/Contents/Info" NSUIElement 1
defaults write "$install_dir/Contents/Info" CFBundleIconFile "octave"
defaults write "$install_dir/Contents/Info" CFBundleIdentifier org.octave.Octave 
defaults write "$install_dir/Contents/Info" CFBundleShortVersionString "$oct_ver"
defaults write "$install_dir/Contents/Info" CFBundleVersion "$oct_ver_string"
defaults write "$install_dir/Contents/Info" NSHumanReadableCopyright "$oct_copy"
defaults write "$install_dir/Contents/Info" CFBundleDocumentTypes -array '{"CFBundleTypeExtensions" = ("m"); "CFBundleTypeOSTypes" = ("Mfile"); "CFBundleTypeRole" = "Editor";}'    
plutil -convert xml1 "$install_dir/Contents/Info.plist"
chmod a=r "$install_dir/Contents/Info.plist"

# collect dependencies from the homebrew database
# clean up the strings using sed
touch "$install_dir/Contents/Resources/DEPENDENCIES"
for f in $(./brew deps octave)
do
	./brew info $f | sed -e 's$homebrew/science/$$g'| sed -e 's$: .*$$g' | sed -e 's$/Applications.*$$g' | head -n3 >> "$install_dir/Contents/Resources/DEPENDENCIES"
	echo "" >> "$install_dir/Contents/Resources/DEPENDENCIES"
done

# change owner
# chown -R admin:wheel /Applications/Octave.app/

# create a nice dmg disc image with create-dmg (MIT License)
if [ "$build_dmg" == "y" ]; then
	# get make-dmg from github
	tmp_dir=$(mktemp -d /tmp/octave-XXXX)
	git clone https://github.com/schoeps/create-dmg.git $tmp_dir/create-dmg

	# get background image
	curl https://raw.githubusercontent.com/schoeps/octave_installer/master/background.tiff -o "$tmp_dir/background.tiff"

	# running create-dmg; this may issue warnings if run headless. However, the dmg
	# will still be created, only some beautifcation cannot be applied
	cd $tmp_dir/create-dmg
	./create-dmg \
	--volname "Octave-Installer" \
	--volicon "$install_dir/Contents/Resources/octave.icns" \
	--window-size 550 442 \
	--icon-size 48 \
	--icon Octave.app 125 180 \
	--hide-extension Octave.app \
	--app-drop-link 415 180 \
	--eula "$install_dir/Contents/Resources/usr/opt/octave/README" \
	--add-file COPYING "$install_dir/Contents/Resources/usr/opt/octave/COPYING" 126 300 \
	--add-file DEPENDENCIES "$install_dir/Contents/Resources/DEPENDENCIES" 415 300 \
	--disk-image-size 1250 \
	--background "$tmp_dir/background.tiff" \
	"$dmg_dir/Octave-Installer.dmg" \
	"$install_dir" 

	echo DMG ready: $dmg_dir/Octave-Installer.dmg
fi

Known limitations

Plotting should work with gnuplot and qt backends. However, sometimes it takes a long time to initialize.