Skip to content

Instantly share code, notes, and snippets.

@dakcarto
Created June 9, 2012 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dakcarto/2902743 to your computer and use it in GitHub Desktop.
Save dakcarto/2902743 to your computer and use it in GitHub Desktop.
Bash script for local builds of QGIS on Mac OS X Snow Leopard and Lion
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Build Mac OS X QGIS.app locally from github.com QGIS repository.
# Keep a number of dmg archives of previous builds as backups.
#
# **Run via sudo, if not installing under home directory**
#
# YOU MUST HAVE FORKED QGIS GITHUB.COM REPOSITORY AND CLONED IT TO YOUR MAC.
#
# Script will optional stash any uncommitted changes in your working tree,
# switch to master branch (you're not working in master branch, right?), merge
# any changes in master branch from QGIS's github.com repository to local
# master branch, apply any build-specific patch file to master, build master,
# reverse any applied patch file, switch back to previous branch and finally
# apply/delete any stash to your working tree, drop you back to your
# previous work state in the branch you were working on, and finally archive
# any built QGIS.app for later reference and comparison to later builds.
#
# Archived builds can be run from disk images later. No need to reinstall.
#
# Tested under Lion 10.7.4 and Snow Leopard 10.6.8
# Requires same kyngchaos.com framework setup and build environment as per
# https://github.com/qgis/Quantum-GIS/blob/master/INSTALL
###############################################################################
## Run settings
###############################################################################
# Mac system python version (use 2.6 for Snow Leopard)
PYVER="2.7"
# Git setup
GIT=/usr/local/git/bin/git
GITURL="git://github.com/qgis/Quantum-GIS.git"
# Working directory and output locations
USERDIR=$(echo ~)
QGISDIR="$USERDIR/QGIS"
GITHUBDIR="$QGISDIR/github.com"
QGISGITDIR="$GITHUBDIR/Quantum-GIS"
BUILDDIR="$GITHUBDIR/build"
TARGETDIR="QGIS_Apps"
APPTARGETDIR="$GITHUBDIR/$TARGETDIR"
APPTARGET="$APPTARGETDIR/QGIS.app"
BROWSER="QGIS Browser.app"
BROWSERLINK="QGIS Browser(symlink).app"
APPARCHIVES="$GITHUBDIR/archives"
# Name for remote to pull from: qgisupstream
# Remote 'qgisupstream' for QGIS and 'origin' for fork cloned from github.com.
# Script will add 'qgisupstream' remote to your local repository, if missing.
# Stashing will happen if there are uncommited changes in current branch.
# GITSTASH=0 will cause script to exit if there are uncommited changes instead.
# TODO: add stashing for current branch, even if not pulling ?
GITPULL=0
GITSTASH=1
BUILDBRANCH='master'
REMOTEBRANCH='master'
# Path to git patch file to apply to branch after switching to it.
# This patch will be applied to branch, branch built, then patch reversed.
# This is useful for getting a branch to build if it won't, yet,
# but reverses the fix and leaves the branch clean after build.
# If QGISTESTS=TRUE, the patch will not be removed, so tests can recompile.
# In such a case, patch will have to be manually removed before next compile.
#GITPATCH="$GITHUBDIR/patches/qgsspatialindex_patch.diff"
GITPATCH=""
# Use maximum number of CPU cores for compiling QGIS.
CPUCORES=$(/usr/sbin/sysctl -n hw.ncpu)
#CPUCORES=4
BUILDQGIS=1
# Whether to clean (rm -R dir && mkdir) build dir before building
CLEANBUILD=0
QGISDEBUG=1
QGISSERVER="TRUE"
QGISAPIDOCS="FALSE"
INSTALLQGIS=1
# Other config build options.
QGISTESTS="TRUE"
BUILDTYPE="MinSizeRel"
MACBUNDLE="2"
if [ $QGISDEBUG -gt 0 ]; then
BUILDTYPE="RelWithDebInfo"
# TODO: test if lesser bundling setting helps debugging
MACBUNDLE="2"
fi
# This is an optional Python module and .dylib to support QScintilla.
# It must be installed to be included. I use it to support my plugin work.
ADDQSCI=1
# Launch the app, archiving happens after launch
LAUNCHAPP=1
ARCHIVEAPP=0
KEEPARCHIVES=5
###############################################################################
## Functions
###############################################################################
REVPATCH () {
cd "$QGISGITDIR"
echo -e "\nReversing patch to branch..."
$GIT apply --reverse "$GITPATCH"
if [ $? -gt 0 ]; then
echo -e "\nERROR reversing patch to branch!"
exit 1
fi
}
###############################################################################
## Start Build
###############################################################################
echo -e "QGIS Build @ $(date)"
###############################################################################
## Update QGIS Working Tree
###############################################################################
cd "$GITHUBDIR"
if [ $GITPULL -gt 0 ]; then
# check for existing repo dir
if [ ! -d "$QGISGITDIR" ]; then
echo -e "\nRepository missing. Using your github.com account, fork and/or clone QGIS repository.\n"
exit 1
else
cd "$QGISGITDIR"
echo -e "\nRepository exists, checking for 'qgisupstream' remote..."
if [ $($GIT remote | egrep -c '^qgisupstream$') -gt 0 ]; then
echo "found remote"
else
$GIT remote add qgisupstream "$GITURL"
if [ $? -gt 0 ]; then
echo -e "\nERROR adding remote qgisupstream to local repository!"
exit 1
fi
echo "added remote"
fi
echo -e "\nSaving name of current branch..."
CURBRANCH=$($GIT branch | grep '*' | sed 's/* //')
echo -e "\nChecking for uncommited changes in working tree...\n"
HASSTASH=0
GITSTAT=$($GIT diff --shortstat | grep -c '.')
if [ $GITSTAT -gt 0 ]; then
if [ $GITSTASH -eq 0 ]; then
echo -e "\nERROR uncommited changes in current branch!"
echo -e "Can't switch to master until changes are committed, stashed or otherwise handled."
exit 1
else
echo -e "Stashing found uncommited changes..."
STASHED=$($GIT stash | egrep -c '^No local changes')
if [ $STASHED -gt 0 ]; then
echo -e "Nothing stashed"
else
echo -e "Stash created"
HASSTASH=1
fi
fi
else
echo -e "Nothing found"
fi
if [ $CURBRANCH != $BUILDBRANCH ]; then
echo -e "\nChecking out local '$BUILDBRANCH' branch..."
# this will DELETE ANY CHANGES to current working tree!
ISBUILDBRANCH=$($GIT branch | grep -c '^* $BUILDBRANCH')
if [ $ISBUILDBRANCH -eq 0 ]; then
$GIT checkout -f $BUILDBRANCH
if [ $? -gt 0 ]; then
echo -e "\nERROR checking out local '$BUILDBRANCH' branch!"
exit 1
fi
fi
fi
# update build branch
echo -e "\nFetching 'qgisupstream' and pulling '$REMOTEBRANCH' branch..."
$GIT fetch qgisupstream
if [ $? -gt 0 ]; then
echo -e "\nERROR fetching git repository!"
exit 1
fi
$GIT pull qgisupstream $REMOTEBRANCH
if [ $? -gt 0 ]; then
echo -e "\nERROR pulling qgisupstream git repo '$REMOTEBRANCH' branch!"
exit 1
fi
fi
fi
if [ "$GITPATCH" != "" ]; then
cd "$QGISGITDIR"
echo -e "\nPatching branch, checking patch validity...\n"
$GIT apply --check "$GITPATCH"
if [ $? -gt 0 ]; then
echo -e "\nERROR patch cannot be applied to branch!"
exit 1
else
echo -e "Patch can be applied, applying..."
fi
$GIT apply "$GITPATCH"
if [ $? -gt 0 ]; then
echo -e "\nERROR applying patch to branch!"
exit 1
fi
fi
if [ "$SUDO_USER" != "" ]; then
chown -R $SUDO_USER "$QGISGITDIR"
fi
###############################################################################
## Build QGIS.app
###############################################################################
if [ $BUILDQGIS -gt 0 ]; then
if [ $CLEANBUILD -gt 0 ]; then
# remove any previous build dir
if [ -d "$BUILDDIR" ]; then
echo -e "\nRemoving existing build directory ..."
rm -R "$BUILDDIR"
fi
fi
if [ ! -d "$BUILDDIR" ]; then
# QGIS CMake setup supports out-of-source dir builds
# make build directory and configure
mkdir -p "$BUILDDIR"
fi
cd "$BUILDDIR"
# use ccache symlinked compilers
export PATH=/usr/local/bin/compilers:$PATH
# force llvm instead of clang under XCode 4.4.1
export CC=/usr/bin/llvm-gcc
export CXX=/usr/bin/llvm-g++
echo -e "\nConfiguring build ...\n"
/usr/local/bin/cmake -D CMAKE_INSTALL_PREFIX="$APPTARGETDIR" \
-D CMAKE_BUILD_TYPE="$BUILDTYPE" \
-D WITH_ASTYLE=TRUE \
-D ENABLE_TESTS="$QGISTESTS" \
-D WITH_INTERNAL_SPATIALITE=FALSE \
-D WITH_PYSPATIALITE=FALSE \
-D SPATIALINDEX_INCLUDE_DIR=/usr/local/include/spatialindex \
-D SPATIALINDEX_LIBRARY=/usr/local/lib/libspatialindex.dylib \
-D QWT_LIBRARY=/usr/local/qwt-5.2.2/lib/libqwt.dylib \
-D QWT_INCLUDE_DIR=/usr/local/qwt-5.2.2/include \
-D BISON_EXECUTABLE=/usr/local/bin/bison \
-D WITH_MAPSERVER="$QGISSERVER" \
-D WITH_APIDOC="$QGISAPIDOCS" \
-D QGIS_MACAPP_BUNDLE="$MACBUNDLE" \
"$QGISGITDIR"
if [ $? -gt 0 ]; then
echo -e "\nERROR configuring QGIS build!"
if [ "$GITPATCH" != "" ]; then REVPATCH; fi
exit 1
fi
# exit 0
#Build and install target QGIS.app
echo -e "\nMaking build ...\n"
make -j $CPUCORES
if [ $? -gt 0 ]; then
echo -e "\nERROR building QGIS!"
if [ "$GITPATCH" != "" ]; then REVPATCH; fi
exit 1
fi
if [ "$SUDO_USER" != "" ]; then
chown -R $SUDO_USER "$BUILDDIR"
fi
if [ $INSTALLQGIS -gt 0 ]; then
if [ ! -d "$APPTARGETDIR" ]; then
mkdir -p "$APPTARGETDIR"
fi
# remove last app built if current repo built
if [ -d "$APPTARGET" ]; then
echo -e "\nRemoving previously installed app ..."
rm -R "$APPTARGET"
fi
# # remove existing alias to QGIS Broswer
# if [ -e "$APPTARGETDIR/$BROWSER" ]; then
# echo -e "\nRemoving previous browser app alias ..."
# rm "$APPTARGETDIR/$BROWSER"
# fi
echo -e "\nInstalling build to target app bundle...\n"
# only show errors
make install 1>/dev/null
if [ $? -gt 0 ]; then
echo -e "\nERROR installing QGIS!"
if [ "$GITPATCH" != "" ]; then REVPATCH; fi
exit 1
fi
# # remove build alias to QGIS Broswer
# echo -e "\nRemoving browser app alias, in favor of symlink for archive..."
# /usr/bin/find "$APPTARGETDIR" -type f -name "$BROWSER*" -delete
# make link to QGIS Browser.app
# link is better than alias, when launching from dmg
# if [ -e "$APPTARGETDIR/$BROWSERLINK" ]; then
# echo -e "\nRemoving previous browser app link ..."
# rm "$APPTARGETDIR/$BROWSERLINK"
# fi
echo -e "\nCreating symbolic link to QGIS Browser.app ..."
# /usr/bin/osascript > /dev/null <<EOT
# tell application "Finder"
# set srcPath to POSIX file "$APPTARGET/Contents/MacOS/bin/QGIS Browser.app" as string
# set trgtPath to POSIX file "$APPTARGETDIR" as string
# make new alias file to application file srcPath at folder trgtPath
# end tell
# EOT
cd "$APPTARGETDIR"
ln -fs "QGIS.app/Contents/MacOS/bin/$BROWSER" "./$BROWSERLINK"
if [ $? -gt 0 ]; then
echo -e "\nERROR creating link!"
fi
echo -e "\nCompiling app's python modules ..."
PY="/usr/bin/python$PYVER"
PYDIR="$APPTARGET/Contents/Resources/python"
PYCOMPILE=/System/Library/Frameworks/Python.framework/Versions/$PYVER/lib/python$PYVER/compileall.pyc
# show only errors
$PY $PYCOMPILE $PYDIR 1>/dev/null
fi
fi
###############################################################################
## Reset repository
###############################################################################
# Reverse any patch file applied to current or master branch.
# Unless building for tests, in which case leave it, then manually removed.
if [ "$GITPATCH" != "" ] && [ "$QGISTESTS" != "TRUE" ]; then
REVPATCH
fi
if [ $GITPULL -gt 0 ]; then
# Checkout previous branch, unless running tests.
if [ $CURBRANCH != $BUILDBRANCH ]; then
echo -e "\nChecking out $CURBRANCH branch..."
$GIT checkout -f $CURBRANCH
if [ $? -gt 0 ]; then
echo -e "\nERROR checking out local $CURBRANCH branch!"
exit 1
fi
fi
# Apply and delete any stash to previous branch
if [ $GITSTASH -gt 0 ] && [ $HASSTASH -gt 0 ]; then
echo -e "\nApplying and removing stash..."
$GIT stash pop
if [ $? -gt 0 ]; then
echo -e "\nERROR applying and removing stash!"
exit 1
fi
fi
fi
if [ "$SUDO_USER" != "" ]; then
chown -R $SUDO_USER "$QGISGITDIR"
fi
###############################################################################
## Add QScintilla Python module to QGIS.app
###############################################################################
if [ $ADDQSCI -gt 0 ] && [ $INSTALLQGIS -gt 0 ]; then
echo -e "\nAdding Qsci.so module to target..."
# cd $QSCIPATH
# QSCIPATH="$QGISDIR/Qsci_Lion/4.8.0"
# /bin/cp -a Qsci.so "$APPTARGET/Contents/Resources/python/PyQt4/";
# /bin/cp -a libqscintilla2.* "$APPTARGET/Contents/MacOS/lib/";
# /usr/bin/install_name_tool -add_rpath @executable_path/lib/libqscintilla2.8.dylib "$APPTARGET/Contents/MacOS/QGIS"
QSCI=''
if [ -e /Library/Python/$PYVER/site-packages/PyQt4/Qsci.so ]; then
QSCI=/Library/Python/$PYVER/site-packages/PyQt4/Qsci.so
fi
if [ -n $QSCI ]; then
cd "$APPTARGET/Contents/Resources/python/PyQt4"
cp -a $QSCI .
install_name_tool -change /Library/Frameworks/libqscintilla2.8.dylib \
@loader_path/../../../MacOS/lib/libqscintilla2.8.dylib Qsci.so
install_name_tool -change QtCore.framework/Versions/4/QtCore \
@loader_path/../../../MacOS/../Frameworks/QtCore.framework/QtCore Qsci.so
install_name_tool -change QtGui.framework/Versions/4/QtGui \
@loader_path/../../../MacOS/../Frameworks/QtGui.framework/QtGui Qsci.so
cd "$APPTARGET/Contents/MacOS/lib"
cp -a /Library/Frameworks/libqscintilla*.dylib .
install_name_tool -change QtCore.framework/Versions/4/QtCore \
@loader_path/../../Frameworks/QtCore.framework/QtCore libqscintilla2.8.dylib
install_name_tool -change QtGui.framework/Versions/4/QtGui \
@loader_path/../../Frameworks/QtGui.framework/QtGui libqscintilla2.8.dylib
install_name_tool -add_rpath @executable_path/lib/libqscintilla2.8.dylib \
"$APPTARGET/Contents/MacOS/QGIS"
else
echo -e "\nQsci.so module not found..."
fi
fi
if [ "$SUDO_USER" != "" ] && [ $BUILDQGIS -gt 0 ] && [ $INSTALLQGIS -gt 0 ]; then
# set permissions on app
chown -R $SUDO_USER "$APPTARGETDIR"
fi
###############################################################################
## Launch app before any archive operation
###############################################################################
if [ $LAUNCHAPP -gt 0 ]; then
echo -e "\nLaunching app..."
sleep 3
/usr/bin/open "$APPTARGET"
fi
###############################################################################
## Archive QGIS.app to DMG Archives
###############################################################################
if [ $ARCHIVEAPP -gt 0 ]; then
sleep 5
# make sure dir structure exists
if [ ! -d "$APPARCHIVES" ]; then
mkdir -p "$APPARCHIVES"
fi
# congifure archive file
cd "$QGISGITDIR"
# this creates a short sha hash for last commit on current local branch built
REVHASH=$($GIT log --no-color --pretty=format:%H $BUILDBRANCH^..$BUILDBRANCH)
REVSHORTHASH=$(echo ${REVHASH:0:7})
REVMAJOR=$(grep 'SET(CPACK_PACKAGE_VERSION_MAJOR' CMakeLists.txt | egrep -o '[0-9]+')
REVMINOR=$(grep 'SET(CPACK_PACKAGE_VERSION_MINOR' CMakeLists.txt | egrep -o '[0-9]+')
REVPATCH=$(grep 'SET(CPACK_PACKAGE_VERSION_PATCH' CMakeLists.txt | egrep -o '[0-9]+')
VERNUM="${REVMAJOR}-${REVMINOR}-${REVPATCH}"
TODAY=$(date -u +"%Y-%m-%d")
if [ "$BUILDBRANCH" != "master" ]; then
APPNAME="QGIS_${VERNUM}_${TODAY}_${REVSHORTHASH}($BUILDBRANCH)"
else
APPNAME="QGIS_${VERNUM}_${TODAY}_${REVSHORTHASH}"
fi
APPARCHIVE="$APPNAME.dmg"
OLDARCHIVE="$APPNAME_old.dmg"
# archive built app to compressed dmg
echo -e "\nArchiving $TARGETDIR target directory...\n"
cd "$GITHUBDIR"
# keep at least 1 archive with same hash, in case build fails
if [ -e "$APPARCHIVES/$APPARCHIVE" ]; then
if [ -e "$APPARCHIVES/$OLDARCHIVE" ]; then
rm "$APPARCHIVES/$OLDARCHIVE"
fi
mv "$APPARCHIVES/$APPARCHIVE" "$APPARCHIVES/$OLDARCHIVE"
fi
# create dmg archive
hdiutil create -nospotlight -skipunreadable -noanyowners \
-srcfolder "$APPTARGETDIR" "$APPARCHIVES/$APPARCHIVE"
if [ $? -gt 0 ]; then
echo -e "\nERROR archiving QGIS to dmg!"
exit 1
fi
if [ "$SUDO_USER" != "" ]; then
chown -R $SUDO_USER "$APPARCHIVES"
fi
# prune dmg archives
CURFILE=1
for f in $( ls -1t "$APPARCHIVES" ); do
if [ $CURFILE -gt $KEEPARCHIVES ]; then
rm "$APPARCHIVES/$f"
fi
let CURFILE=CURFILE+1
done
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment