diff --git a/scripts/build-osx-app-template.sh b/scripts/build-osx-app-template.sh
deleted file mode 100755
index 71ada60fbdb..00000000000
--- a/scripts/build-osx-app-template.sh
+++ /dev/null
@@ -1,442 +0,0 @@
-#!/bin/bash -e
-#
-# Create (build) an Orange application bundle template
-#
-# example usage:
-#
-# $ build-osx-app-template.sh $HOME/Orange.app
-#
-# Prerequisites:
-# a working gcc compiler toolchain
-# a working gfortran compiler on PATH
-
-
-function print_usage () {
-echo 'build-osx-app-template.sh [-t] [-k] some_path/Orange.app
-
-Build an Orange application template from scratch.
-This will download and build all requirements (Python, Qt4, ...) for a
-standalone .app distribution.
-
-Warning: This will take a lot of time.
-
-Options:
- -t --build-temp DIR A temporary build directory.
- -k --keep-temp Do not delete the temp directory after build.
- -h --help Print this help.
-'
-}
-
-while [[ ${1:0:1} = "-" ]]; do
- case $1 in
- -t|--build-temp)
- BUILD_TEMP=$2
- shift 2
- ;;
- -k|--keep-temp)
- KEEP_TEMP=1
- shift 1
- ;;
- -h|--help)
- print_usage
- exit 0
- ;;
- -*)
- echo "Unknown option $1" >&2
- print_usage
- exit 1
- ;;
- esac
-done
-
-
-if [[ $BUILD_TEMP ]]; then
- mkdir -p "$BUILD_TEMP"
-else
- BUILD_TEMP=$(mktemp -d -t build-template)
-fi
-
-APP=$1
-
-if [[ ! $APP ]]; then
- echo "Target application path must be specified" >&2
- print_usage
- exit 1
-fi
-
-mkdir -p "$APP"
-
-# Convert to absolute path
-APP=$(cd "$APP"; pwd)
-
-# Get absolute path to the bundle-lite template
-SCRIPT_DIR_NAME=$(dirname "$0")
-# Convert to absolute path
-SCRIPT_DIR_NAME=$(cd "$SCRIPT_DIR_NAME"; pwd)
-BUNDLE_LITE=$SCRIPT_DIR_NAME/bundle-lite/Orange.app
-
-
-# Versions of included 3rd party software
-
-PYTHON_VER=3.4.3
-PIP_VER=6.1.1
-
-NUMPY_VER=1.9.2
-SCIPY_VER=0.15.1
-
-QT_VER=4.8.7
-SIP_VER=4.16.2
-PYQT_VER=4.11.1
-SCIKIT_LEARN_VER=0.16.1
-
-# Number of make jobs
-MAKE_JOBS=${MAKE_JOBS:-$(sysctl -n hw.physicalcpu)}
-
-PYTHON=$APP/Contents/MacOS/python
-EASY_INSTALL=$APP/Contents/MacOS/easy_install
-PIP=$APP/Contents/MacOS/pip
-
-export MACOSX_DEPLOYMENT_TARGET=10.6
-
-SDK=/Developer/SDKs/MacOSX$MACOSX_DEPLOYMENT_TARGET.sdk
-
-function create_template {
- # Create a minimal .app template with the expected dir structure
- # Info.plist and icons.
-
- mkdir -p "$APP"
- mkdir -p "$APP"/Contents/MacOS
- mkdir -p "$APP"/Contents/Resources
-
- # Copy icons and Info.plist
- cp "$BUNDLE_LITE"/Contents/Resources/* "$APP"/Contents/Resources
- cp "$BUNDLE_LITE"/Contents/Info.plist "$APP"/Contents/Info.plist
-
- cp "$BUNDLE_LITE"/Contents/PkgInfo "$APP"/Contents/PkgInfo
-
- cat <<-'EOF' > "$APP"/Contents/MacOS/ENV
- # Create an environment for running python from the bundle
- # Should be run as "source ENV"
-
- BUNDLE_DIR=`dirname "$0"`/../
- BUNDLE_DIR=`perl -MCwd=realpath -e 'print realpath($ARGV[0])' "$BUNDLE_DIR"`/
- FRAMEWORKS_DIR="$BUNDLE_DIR"Frameworks/
- RESOURCES_DIR="$BUNDLE_DIR"Resources/
-
- PYVERSION="3.4"
-
- PYTHONEXECUTABLE="$FRAMEWORKS_DIR"Python.framework/Resources/Python.app/Contents/MacOS/Python
- PYTHONHOME="$FRAMEWORKS_DIR"Python.framework/Versions/"$PYVERSION"/
-
- DYLD_FRAMEWORK_PATH="$FRAMEWORKS_DIR"${DYLD_FRAMEWORK_PATH:+:$DYLD_FRAMEWORK_PATH}
-
- export PYTHONEXECUTABLE
- export PYTHONHOME
- export PYTHONNOUSERSITE=1
-
- export DYLD_FRAMEWORK_PATH
-
- # Some non framework libraries are put in $FRAMEWORKS_DIR by machlib standalone
- export DYLD_LIBRARY_PATH="$FRAMEWORKS_DIR"${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}
- export GVBINDIR="$BUNDLE_DIR"/Resources/graphviz/lib/graphviz
- export PATH="$PATH":"$BUNDLE_DIR"/MacOS
-EOF
-
-}
-
-function install_python() {
- download_and_extract "https://www.python.org/ftp/python/$PYTHON_VER/Python-$PYTHON_VER.tgz"
-
- pushd Python-$PYTHON_VER
-
- # _hashlib import fails with Symbol not found: _EVP_MD_CTX_md
- # The 10.5 sdk's libssl does not define it (even though it is v 0.9.7)
- patch setup.py -i - <<-'EOF'
- 834c834
- < min_openssl_ver = 0x00907000
- ---
- > min_openssl_ver = 0x00908000
-EOF
-
- ./configure --enable-framework="$APP"/Contents/Frameworks \
- --prefix="$APP"/Contents/Resources \
- --with-universal-archs=intel \
- --enable-ipv6 \
- --enable-universalsdk="$SDK"
-
- make -j $MAKE_JOBS
-
- # We don't want to install IDLE.app, Python Launcher.app, in /Applications
- # on the build system.
- make install PYTHONAPPSDIR="$(pwd)"
-
- popd
-
- # PythonAppStart will be used for starting the application GUI.
- # This needs to be symlinked here for Desktop services to read the app's
- # Info.plist and not the contained Python.app's
- ln -fs ../Frameworks/Python.framework/Resources/Python.app/Contents/MacOS/Python "$APP"/Contents/MacOS/PythonAppStart
- ln -fs ../Frameworks/Python.framework/Resources/Python.app "$APP"/Contents/Resources/Python.app
-
- cat <<-'EOF' > "$APP"/Contents/MacOS/python
- #!/bin/bash
-
- DIRNAME=$(dirname "$0")
-
- # Set the proper env variables
- source "$DIRNAME"/ENV
-
- exec -a "$0" "$PYTHONEXECUTABLE" "$@"
-EOF
-
- chmod +x "$APP"/Contents/MacOS/python
-
- # Test it
- "$PYTHON" -c"import sys, hashlib"
-
- # Install pip/setuptools
- "$PYTHON" -m ensurepip
- "$PYTHON" -m pip install pip==$PIP_VER
-
- create_shell_start_script pip
- create_shell_start_script easy_install
-}
-
-
-function install_ipython {
- "$PIP" install ipython
- create_shell_start_script ipython
-}
-
-function install_qt4 {
- QT_VER_SHORT=${QT_VER%%\.[0-9]}
-
- # 4.8.* (4.8 does not compile for x86_64 using 10.5 SDK)
- download_and_extract "http://download.qt-project.org/official_releases/qt/$QT_VER_SHORT/$QT_VER/qt-everywhere-opensource-src-$QT_VER.tar.gz"
-
- # 4.7 or older
- #download_and_extract "http://download.qt-project.org/archive/qt/$QT_VER_SHORT/qt-everywhere-opensource-src-$QT_VER.tar.gz"
-
- pushd qt-everywhere-opensource-src-$QT_VER
-
- yes yes | ./configure -prefix "$APP"/Contents/Resources/Qt4 \
- -libdir "$APP"/Contents/Frameworks \
- -framework \
- -release \
- -opensource \
- -no-qt3support \
- -arch x86 -arch x86_64 \
- -no-sql-psql \
- -no-sql-ibase \
- -no-sql-mysql \
- -no-sql-odbc \
- -no-sql-sqlite \
- -no-sql-sqlite2 \
- -nomake examples \
- -nomake demos \
- -nomake docs \
- -nomake translations \
- -sdk "$SDK"
-
- make -j $MAKE_JOBS
- make install
-
- # Register plugins with Qt.
- cat <<-EOF > "$APP"/Contents/Resources/qt.conf
- [Paths]
- Plugins = Resources/Qt4/plugins
-EOF
-
- # In case the Python executable is invoked directly (not through
- # Contents/MacOS/python) we also want it to find the plugins.
- cat <<-EOF > "$APP"/Contents/Frameworks/Python.framework/Resources/Python.app/Contents/Resources/qt.conf
- [Paths]
- Plugins = ../../../../../Resources/Qt4/plugins
-EOF
-
- popd
-}
-
-function install_sip {
- download_and_extract "http://sourceforge.net/projects/pyqt/files/sip/sip-$SIP_VER/sip-$SIP_VER.tar.gz"
- pushd sip-$SIP_VER
-
- "$PYTHON" configure.py --arch i386 --arch x86_64 --sdk "$SDK"
-
- make -j $MAKE_JOBS
- make install
-
- "$PYTHON" -c"import sip"
-
- popd
-
-}
-
-function install_pyqt4 {
- download_and_extract "http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-$PYQT_VER/PyQt-mac-gpl-$PYQT_VER.tar.gz"
- pushd PyQt-mac-gpl-$PYQT_VER
-
- yes yes | "$PYTHON" configure.py --qmake "$APP"/Contents/Resources/Qt4/bin/qmake
-
- make -j $MAKE_JOBS
- make install
-
- "$PYTHON" -c"from PyQt4 import QtGui, QtWebKit, QtSvg, QtNetwork"
-
- popd
-}
-
-
-function install_graphviz {
- download_and_extract "http://graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.38.0.tar.gz"
- pushd graphviz-2.38.0
- ./configure --prefix "$APP"/Contents/Resources/graphviz/ \
- --with-quartz \
- --without-qt \
- --disable-swig \
- --without-pangocairo \
- --without-freetype2 \
- --without-x \
- --without-rsvg \
- --without-devil \
- --without-webp \
- --without-popler \
- --without-ghostscript \
- --without-lasi \
- --without-fontconfig \
- --without-gdk \
- --without-gtk \
- --without-glut
-
- make
- make install
- (
- cd "$APP"/Contents/MacOS/
- ln -sf ../Resources/graphviz/bin/* ./
- )
- popd
-}
-
-function install_numpy {
- "$PIP" install numpy==$NUMPY_VER
-
- "$PYTHON" -c"import numpy"
-}
-
-function install_scipy {
- # This is tricky (req gfortran)
- "$PIP" install scipy==$SCIPY_VER
-
- "$PYTHON" -c"import scipy"
-}
-
-function install_scikit_learn {
- "$PIP" install scikit-learn==$SCIKIT_LEARN_VER
-
- "$PYTHON" -c"import sklearn"
-}
-
-function install_psycopg2 {
- "$PIP" install psycopg2
-
- "$PYTHON" -c"import psycopg2"
-}
-
-function download_and_extract() {
- # Usage: download_and_extract http://example/source.tar.gz
- #
- # Download the specified .tar source package and extract it in the current dir
- # If the source package is already present only extract it
-
- URL=$1
- if [[ ! $URL ]]; then
- echo "An url expected"
- exit 1
- fi
-
- SOURCE_TAR=$(basename "$URL")
-
- if [[ ! -e $SOURCE_TAR ]]; then
- echo "Downloading $SOURCE_TAR"
- curl --fail -L --max-redirs 3 "$URL" -o "$SOURCE_TAR".part
- mv "$SOURCE_TAR".part "$SOURCE_TAR"
- fi
- tar -xzf "$SOURCE_TAR"
-}
-
-
-function create_shell_start_script() {
- # Usage: create_shell_start_script pip
- #
- # create a start script for the specified script in $APP/Contents/MacOS
-
- SCRIPT=$1
-
- cat <<-'EOF' > "$APP"/Contents/MacOS/"$SCRIPT"
- #!/bin/bash
-
- DIRNAME=$(dirname "$0")
- NAME=$(basename "$0")
-
- # Set the proper env variables
- source "$DIRNAME"/ENV
-
- exec -a "$0" "$DIRNAME"/python "$FRAMEWORKS_DIR"/Python.framework/Versions/Current/bin/"$NAME" "$@"
-EOF
-
- chmod +x "$APP"/Contents/MacOS/"$SCRIPT"
-}
-
-function cleanup {
- # Cleanup the application bundle by removing unnecesary files.
- find "$APP"/Contents/ \( -name '*~' -or -name '*.bak' -or -name '*.pyc' -or -name '*.pyo' \) -delete
-
- find "$APP"/Contents/Frameworks -name '*_debug*' -delete
- find "$APP"/Contents/Frameworks -name '*.la' -delete
- find "$APP"/Contents/Frameworks -name '*.a' -delete
- find "$APP"/Contents/Frameworks -name '*.prl' -delete
-}
-
-function make_standalone {
- "$PIP" install macholib==1.5.1
- "$PYTHON" -m macholib standalone "$APP"
- "$PIP" uninstall --yes altgraph
- "$PIP" uninstall --yes macholib
-}
-
-pushd "$BUILD_TEMP"
-
-echo "Building template in $BUILD_TEMP"
-echo
-
-create_template
-
-install_python
-
-install_numpy
-
-install_scipy
-
-install_qt4
-
-install_sip
-
-install_pyqt4
-
-install_ipython
-
-install_scikit_learn
-
-install_psycopg2
-
-install_graphviz
-
-make_standalone
-
-cleanup
-
-popd
-
-if [[ ! $KEEP_TEMP ]]; then
- rm -rf $BUILD_TEMP
-fi
diff --git a/scripts/build-osx-app.sh b/scripts/build-osx-app.sh
deleted file mode 100755
index 9d954588662..00000000000
--- a/scripts/build-osx-app.sh
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/bin/bash
-# Build an OSX Application (.app) for Orange Canvas
-#
-# Example:
-#
-# $ build-osx-app.sh $HOME/Applications/Orange3.app
-#
-
-set -e
-
-function print_usage() {
- echo 'build-osx-app.sh [-i] [--template] Orange3.app
-
-Build an Orange Canvas OSX application bundle (Orange3.app).
-
-NOTE: this script should be run from the source root directory.
-
-Options:
-
- --template TEMPLATE_URL Path or url to an application template as build
- by "build-osx-app-template.sh. If not provided
- a default one will be downloaded.
- -i --inplace The provided target application path is already
- a template into which Orange should be installed
- (this flag cannot be combined with --template).
- -h --help Print this help'
-}
-
-while [[ ${1:0:1} = "-" ]]; do
- case $1 in
- --template)
- TEMPLATE_URL=$2
- shift 2;
- ;;
- -i|--inplace)
- INPLACE=1
- shift 1
- ;;
- -h|--help)
- print_usage
- exit 0
- ;;
- -*)
- echo "Unknown argument $1"
- print_usage
- exit 1
- ;;
- esac
-done
-
-# extended glob expansion / fail on filename expansion
-shopt -s extglob failglob
-
-if [[ ! -f setup.py ]]; then
- echo "This script must be run from the source root directory!"
- print_usage
- exit 1
-fi
-
-APP=${1:-dist/Orange3.app}
-
-if [[ $INPLACE ]]; then
- if [[ $TEMPLATE_URL ]]; then
- echo "--inplace and --template can not be combined"
- print_usage
- exit 1
- fi
-
- if [[ -e $APP && ! -d $APP ]]; then
- echo "$APP exists and is not a directory"
- print_usage
- exit 1
- fi
-fi
-
-TEMPLATE_URL=${TEMPLATE_URL:-"http://orange.biolab.si/download/files/bundle-templates/Orange3.app-template.tar.gz"}
-
-SCHEMA_REGEX='^(https?|ftp|local)://.*'
-
-if [[ ! $INPLACE ]]; then
- BUILD_DIR=$(mktemp -d -t orange-build)
-
- echo "Retrieving a template from $TEMPLATE_URL"
- # check for a url schema
- if [[ $TEMPLATE_URL =~ $SCHEMA_REGEX ]]; then
- curl --fail --silent --location --max-redirs 1 "$TEMPLATE_URL" | tar -x -C "$BUILD_DIR"
- TEMPLATE=( $BUILD_DIR/*.app )
-
- elif [[ -d $TEMPLATE_URL ]]; then
- cp -a $TEMPLATE_URL $BUILD_DIR
- TEMPLATE=$BUILD_DIR/$(basename "$TEMPLATE_URL")
-
- elif [[ -e $TEMPLATE_URL ]]; then
- # Assumed to be an archive
- tar -xf "$TEMPLATE_URL" -C "$BUILD_DIR"
- TEMPLATE=( $BUILD_DIR/*.app )
- else
- echo "Invalid --template $TEMPLATE_URL"
- exit 1
- fi
-else
- TEMPLATE=$APP
-fi
-echo "Building application in $TEMPLATE"
-
-PYTHON=$TEMPLATE/Contents/MacOS/python
-PIP=$TEMPLATE/Contents/MacOS/pip
-
-PREFIX=$("$PYTHON" -c'import sys; print(sys.prefix)')
-SITE_PACKAGES=$("$PYTHON" -c'import sysconfig as sc; print(sc.get_path("platlib"))')
-
-echo "Installing/updating setuptools and pip"
-echo "======================================"
-"$PIP" install 'setuptools==18.*' 'pip==8.*'
-
-echo "Installing orangeqt"
-echo "==================="
-FDIR=$TEMPLATE/Contents/Frameworks
-# to find moc executable in the app bundle
-EXTRA_PATH=$PREFIX/bin:$TEMPLATE/Contents/Resources/Qt4/bin
-# for the compiler to find Qt's headers and frameworks
-EXTRA_CXXFLAGS="-F$FDIR -I$FDIR/QtCore.framework/Headers -I$FDIR/QtGui.framework/Headers"
-EXTRA_LDFLAGS="-F$FDIR -framework QtCore -framework QtGui"
-
-echo "Fixing sip/pyqt configuration"
-
-sed -i.bak "s@/.*\.app/@$TEMPLATE/@g" "${SITE_PACKAGES}"/PyQt4/pyqtconfig.py
-sed -i.bak "s@/.*\.app/@$TEMPLATE/@g" "${SITE_PACKAGES}"/sipconfig.py
-
-
-(
- PATH=$EXTRA_PATH:$PATH
- CXXFLAGS=${EXTRA_CXXFLAGS}${CXXFLAGS:+:$CXXFLAGS}
- LDFLAGS=${EXTRA_LDFLAGS}:${LDFLAGS:+:$LDFLAGS}
- "$PIP" install qt-graph-helpers
-)
-
-# Explicitly specify a numpy version due to an undeclared dependency of
-# scikit-learn's osx wheel files (0.17.1) which seem to be build against
-# numpy 1.10.*, and require numpy >= 1.10 ABI.
-echo "Installing numpy"
-echo "================"
-
-"$PIP" install --only-binary numpy 'numpy==1.10.*'
-
-echo "Installing Orange"
-echo "================="
-
-"$PIP" install .
-
-echo "Running tests"
-echo "============="
-
-# Run in an empty dir to avoid imports from source checkout
-(
- tmpdir=$(mktemp -d -t temp-test)
- cleanup-on-exit() {
- rm -r $tmpdir
- }
- trap cleanup-on-exit EXIT
- cd "$tmpdir"
- "$PYTHON" -m unittest Orange.tests
-)
-
-
-cat <<-'EOF' > "$TEMPLATE"/Contents/MacOS/Orange
- #!/bin/bash
-
- DIRNAME=$(dirname "$0")
- source "$DIRNAME"/ENV
-
- # LaunchServices passes the Carbon process identifier to the application with
- # -psn parameter - we do not want it
- if [[ $1 == -psn_* ]]; then
- shift 1
- fi
-
- exec -a "$0" "$DIRNAME"/PythonAppStart -m Orange.canvas "$@"
-EOF
-
-chmod +x "$TEMPLATE"/Contents/MacOS/Orange
-
-
-if [[ ! $INPLACE ]]; then
- echo "Moving the application to $APP"
- if [[ -e $APP ]]; then
- rm -rf "$APP"
- fi
- mkdir -p $(dirname "$APP")
- mv "$TEMPLATE" "$APP"
-fi
diff --git a/scripts/bundle-lite/Orange.app/Contents/Info.plist b/scripts/bundle-lite/Orange.app/Contents/Info.plist
deleted file mode 100644
index 8e5a82d30c4..00000000000
--- a/scripts/bundle-lite/Orange.app/Contents/Info.plist
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
- CFBundleExecutable
- Orange
- CFBundleIconFile
- orange.icns
- CFBundleIdentifier
- si.biolab.Orange
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- Orange
- CFBundleGetInfoString
- Orange, component-based data mining software
- CFBundlePackageType
- APPL
- CFBundleSignature
- Orng
- CFBundleShortVersionString
- 1.0.0
- CFBundleVersion
- 1.0.0
- CFBundleDocumentTypes
-
-
- CFBundleTypeExtensions
-
- ows
-
- CFBundleTypeName
- Orange Canvas Schema
- CFBundleTypeOSTypes
-
- OWSf
-
- CFBundleTypeIconFile
- schema.icns
- CFBundleTypeRole
- Viewer
- LSIsAppleDefaultForType
-
-
-
- NSHighResolutionCapable
-
-
-
diff --git a/scripts/macos/README.txt b/scripts/macos/README.txt
new file mode 100644
index 00000000000..2f498de460e
--- /dev/null
+++ b/scripts/macos/README.txt
@@ -0,0 +1,14 @@
+Orange macOS application (.app) build scripts
+---------------------------------------------
+
+Contents:
+
+python-framework.sh
+ Download, unpack and make relocatable the official python.org framework
+ installers (used by build-macos-app.sh)
+
+build-macos-app.sh
+ Build an Orange.app application bundle from scratch
+
+create-dmg-installer.sh
+ Pack the Orange.app applicaiton into a .dmg installer disk image
diff --git a/scripts/macos/build-macos-app.sh b/scripts/macos/build-macos-app.sh
new file mode 100755
index 00000000000..7b9a94785e4
--- /dev/null
+++ b/scripts/macos/build-macos-app.sh
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+
+set -e
+
+usage() {
+ echo 'usage: build-macos-app.sh [--python-version VER] [--pip-arg ARG] APPPATH
+Create (build) an macOS application bundle
+
+Options:
+ --python-version VERSION
+ Python version to install in the application bundle (default: 3.6.1)
+
+ --pip-arg ARG
+ Pip install arguments to populate the python environemnt in the
+ application bundle. Can be used multiple times.
+ If not supplied then by default the latest PyPi published Orange3 and
+ requirements as recorded in scripts/macos/requirements.txt are
+ installed.
+
+ -h|--help
+ Print this help
+
+Examples
+ build-macos-app.sh ~/Applications/Orange3.app
+ Build the application using the latest published version on pypi
+
+ build-macos-app.sh --pip-arg={Orange3==3.3.12,PyQt5} ~/Applications/Orange3.app
+ Build the application using the specified Orange version
+
+ build-macos-app.sh --pip-arg=path-tolocal-checkout ~/Applications/Orange3-Dev.app
+ Build the application using a local source checkout
+
+ build-macos-app.sh --pip-arg={-e,path-tolocal-checkout} ~/Applications/Orange3-Dev.app
+ Build the application and install orange in editable mode
+
+ buils-macos-app.sh --pip-arg={-r,requirements.txt} /Applications/Orange3.app
+ Build the application using a fixed set of locked requirements.
+'
+}
+
+DIR=$(dirname "$0")
+
+# Python version in the bundled framework
+PYTHON_VERSION=3.6.1
+
+# Pip arguments used to populate the python environment in the application
+# bundle
+PIP_REQ_ARGS=( )
+
+while [[ "${1:0:1}" == "-" ]]; do
+ case "${1}" in
+ --python-version=*)
+ PYTHON_VERSION=${1#*=}
+ shift 1;;
+ --python-version)
+ PYTHON_VERSION=${2:?"--python-version requires an argument"}
+ shift 2;;
+ --pip-arg=*)
+ PIP_REQ_ARGS+=( "${1#*=}" )
+ shift 1;;
+ --pip-arg)
+ PIP_REQ_ARGS+=( "${2:?"--pip-arg requires an argument"}" )
+ shift 2;;
+ --help|-h)
+ usage; exit 0;;
+ -*)
+ echo "Invalid argument ${1}" >&2; usage >&2; exit 1;;
+ esac
+done
+
+APPDIR=${1:?"Target APPDIR argument is missing"}
+
+PYVER=${PYTHON_VERSION%.*} # Major.Minor
+
+if [[ ${#PIP_REQ_ARGS[@]} -eq 0 ]]; then
+ PIP_REQ_ARGS+=( Orange3 -r "${DIR}"/requirements.txt )
+fi
+
+mkdir -p "${APPDIR}"/Contents/MacOS
+mkdir -p "${APPDIR}"/Contents/Frameworks
+mkdir -p "${APPDIR}"/Contents/Resources
+
+cp -a "${DIR}"/skeleton.app/Contents/{Resources,Info.plist.in} \
+ "${APPDIR}"/Contents
+
+# Layout a 'relocatable' python framework in the app directory
+"${DIR}"/python-framework.sh \
+ --version "${PYTHON_VERSION}" \
+ "${APPDIR}"/Contents/Frameworks
+
+ln -fs ../Frameworks/Python.framework/Versions/${PYVER}/Resources/Python.app/Contents/MacOS/Python \
+ "${APPDIR}"/Contents/MacOS/PythonApp
+
+ln -fs ../Frameworks/Python.framework/Versions/${PYVER}/bin/python${PYVER} \
+ "${APPDIR}"/Contents/MacOS/python
+
+"${APPDIR}"/Contents/MacOS/python -m ensurepip
+"${APPDIR}"/Contents/MacOS/python -m pip install pip~=9.0 wheel
+
+# Python 3.6 on macOS no longer links the obsolete system libssl.
+# Instead it builds/ships a it's own which expects a cert.pem in hardcoded
+# /Library/Python.framework/3.6/etc/openssl/ path (but does no actually ship
+# the file in the framework installer). Instead a user is prompted during
+# .pkg install to run a script that pip install's certifi and links its
+# cacert.pem to the etc/openssl dir. We do the same but must also patch
+# ssl.py to load the cert store from a python prefix relative path (this is
+# done by python-framework.sh script). Another option would be to export system
+# certificates at runtime using the 'security' command line tool.
+"${APPDIR}"/Contents/MacOS/python -m pip install certifi
+# link the certifi supplied cert store to ${prefix}/etc/openssl/cert.pem
+ln -fs ../../lib/python${PYVER}/site-packages/certifi/cacert.pem \
+ "${APPDIR}"/Contents/Frameworks/Python.framework/Versions/${PYVER}/etc/openssl/cert.pem
+# sanity check
+test -r "${APPDIR}"/Contents/Frameworks/Python.framework/Versions/${PYVER}/etc/openssl/cert.pem
+
+cat <<'EOF' > "${APPDIR}"/Contents/MacOS/Orange
+#!/bin/bash
+
+DIR=$(dirname "$0")
+
+# LaunchServices passes the Carbon process identifier to the application with
+# -psn parameter - we do not want it
+if [[ "${1}" == -psn_* ]]; then
+ shift 1
+fi
+
+# Disable user site packages
+export PYTHONNOUSERSITE=1
+
+exec "${DIR}"/PythonApp -m Orange.canvas "$@"
+EOF
+chmod +x "${APPDIR}"/Contents/MacOS/Orange
+
+cat <<'EOF' > "${APPDIR}"/Contents/MacOS/pip
+#!/bin/bash
+
+DIR=$(dirname "$0")
+
+# Disable user site packages
+export PYTHONNOUSERSITE=1
+
+exec -a "$0" "${DIR}"/python -m pip "$@"
+EOF
+chmod +x "${APPDIR}"/Contents/MacOS/pip
+
+PYTHON="${APPDIR}"/Contents/MacOS/python
+
+"${PYTHON}" -m pip install "${PIP_REQ_ARGS[@]}"
+
+VERSION=$("${PYTHON}" -m pip show orange3 | grep -E '^Version:' |
+ cut -d " " -f 2)
+
+m4 -D__VERSION__="${VERSION:?}" "${APPDIR}"/Contents/Info.plist.in \
+ > "${APPDIR}"/Contents/Info.plist
+
+# Sanity check
+(
+ # run from an empty dir to avoid importing/finding any packages on ./
+ tempdir=$(mktemp -d)
+ cleanup() { rm -r "${tempdir}"; }
+ trap cleanup EXIT
+ cd "${tempdir}"
+ "${PYTHON}" -m pip install --no-cache-dir --no-index orange3 PyQt5
+ "${PYTHON}" -m Orange.canvas --help > /dev/null
+)
\ No newline at end of file
diff --git a/scripts/create-dmg-installer.sh b/scripts/macos/create-dmg-installer.sh
similarity index 50%
rename from scripts/create-dmg-installer.sh
rename to scripts/macos/create-dmg-installer.sh
index 476341bc2e2..7785e75df13 100755
--- a/scripts/create-dmg-installer.sh
+++ b/scripts/macos/create-dmg-installer.sh
@@ -1,9 +1,9 @@
-#!/bin/bash -e
-#
-# Create an .dmg installer for Orange
+#!/bin/bash
+
+set -e
function print_usage() {
- echo 'create-dmg-installer.sh --app BUILD_APP_PATH OUTPUT_BUNDLE.dmg
+ echo 'create-dmg-installer.sh --app BUILD_APP_PATH OUTPUT_BUNDLE.dmg
Create an disk image installer (.dmg) for Orange OSX application.
@@ -16,123 +16,115 @@ Options:
'
}
+
DIRNAME=$(dirname "$0")
# Path to dmg resources (volume icon, background, ...)
-RES=$DIRNAME/dmg-resources
+RES="${DIRNAME}"/dmg-resources
-APP="dist/Orange3.app"
+APP=dist/Orange3.app
KEEP_TEMP=0
-while [[ ${1:0:1} = "-" ]]; do
- case $1 in
+while [[ "${1:0:1}" = "-" ]]; do
+ case "${1}" in
-a|--app)
- APP=$2
- shift 2
- ;;
+ APP=${2:?"BUILD_APP_PATH is missing"}
+ shift 2 ;;
-k|--keep-temp)
KEEP_TEMP=1
- shift 1
- ;;
+ shift 1 ;;
-h|--help)
print_usage
- exit 0
- ;;
+ exit 0 ;;
-*)
- echo "Unknown option $1"
+ echo "Unknown option $1" >&2
print_usage
exit 1
;;
esac
done
-DMG=$1
+DMG=${1?"Output bundle dmg path not specified"}
-if [[ ! $DMG ]]; then
- echo "No output bundle dmg specified."
- print_usage
- exit 1
-fi
-if [[ ! -d $APP ]]; then
+if [[ ! -d "${APP}" ]]; then
echo "$APP path does not exits or is not a directory."
print_usage
exit 1
fi
TMP_DIR=$(mktemp -d -t orange-dmg)
-TMP_TEMPLATE=$TMP_DIR/template
-TMP_DMG=$TMP_DIR/orange.dmg
+TMP_TEMPLATE="${TMP_DIR}"/template
+TMP_DMG="${TMP_DIR}"/orange.dmg
-echo "Preparing an image template in $TMP_TEMPLATE"
+echo "Preparing an image template in ${TMP_TEMPLATE}"
echo "============================================="
# Copy neccessary resources into the template
-mkdir -p "$TMP_TEMPLATE"/.background
-cp -a "$RES"/background.png "$TMP_TEMPLATE"/.background
-cp -a "$RES"/VolumeIcon.icns "$TMP_TEMPLATE"/.VolumeIcon.icns
+mkdir -p "${TMP_TEMPLATE}"/.background
-cp -a "$RES"/DS_Store "$TMP_TEMPLATE"/.DS_Store
+cp -a "${RES}"/background.png "${TMP_TEMPLATE}"/.background
+cp -a "${RES}"/VolumeIcon.icns "${TMP_TEMPLATE}"/.VolumeIcon.icns
+cp -a "${RES}"/DS_Store "${TMP_TEMPLATE}"/.DS_Store
# Create a link to the Applications folder.
-ln -s /Applications/ "$TMP_TEMPLATE"/Applications
+ln -s /Applications/ "${TMP_TEMPLATE}"/Applications
# Copy the .app directory in place
-cp -a "$APP" "$TMP_TEMPLATE"/Orange3.app
+cp -a "${APP}" "${TMP_TEMPLATE}"/Orange3.app
# Remove unnecesary files.
-find "$TMP_TEMPLATE"/Orange3.app/Contents/ \( -name '*~' -or -name '*.bak' -or -name '*.pyc' -or -name '*.pyo' \) -delete
+find "${TMP_TEMPLATE}"/Orange3.app/Contents/ \( -name '*.pyc' -or -name '*.pyo' \) -delete
# Create a regular .fseventsd/no_log file
# (see http://hostilefork.com/2009/12/02/trashes-fseventsd-and-spotlight-v100/ )
-mkdir "$TMP_TEMPLATE"/.fseventsd
-touch "$TMP_TEMPLATE"/.fseventsd/no_log
+mkdir "${TMP_TEMPLATE}"/.fseventsd
+touch "${TMP_TEMPLATE}"/.fseventsd/no_log
echo "Creating a temporary disk image"
hdiutil create -format UDRW -volname Orange -fs HFS+ \
-fsargs "-c c=64,a=16,e=16" \
- -srcfolder "$TMP_TEMPLATE" \
- "$TMP_DMG"
+ -srcfolder "${TMP_TEMPLATE}" \
+ "${TMP_DMG}"
# Force detatch an image it it is mounted
hdiutil detach /Volumes/Orange -force || true
# Mount in RW mode
echo "Mounting temporary disk image"
-MOUNT_OUTPUT=$(hdiutil attach -readwrite -noverify -noautoopen "$TMP_DMG" | egrep '^/dev/')
+MOUNT_OUTPUT=$(hdiutil attach -readwrite -noverify -noautoopen "${TMP_DMG}" |
+ egrep '^/dev/')
-DEV_NAME=$(echo -n "$MOUNT_OUTPUT" | head -n 1 | awk '{print $1}')
-MOUNT_POINT=$(echo -n "$MOUNT_OUTPUT" | tail -n 1 | awk '{print $3}')
+DEV_NAME=$(echo -n "${MOUNT_OUTPUT}" | head -n 1 | awk '{print $1}')
+MOUNT_POINT=$(echo -n "${MOUNT_OUTPUT}" | tail -n 1 | awk '{print $3}')
-echo "Fixing permissions."
-
-chmod -Rf go-w "$TMP_TEMPLATE" || true
+echo "Fixing permissions"
+chmod -Rf go-w "${TMP_TEMPLATE}" || true
# Makes the disk image window open automatically when mounted
-bless -openfolder "$MOUNT_POINT"
+bless -openfolder "${MOUNT_POINT}"
# Hides background directory even more
-SetFile -a V "$MOUNT_POINT/.background/"
+SetFile -a V "${MOUNT_POINT}/.background/"
# Sets the custom icon volume flag so that volume has nice
# Orange icon after mount (.VolumeIcon.icns)
-SetFile -a C "$MOUNT_POINT"
+SetFile -a C "${MOUNT_POINT}"
-hdiutil detach "$DEV_NAME" -force
+hdiutil detach "${DEV_NAME}" -force
echo "Converting temporary image to a compressed image."
-if [[ -e $DMG ]]; then
- rm -f "$DMG"
-fi
+if [[ -e "${DMG}" ]]; then rm -f "${DMG}"; fi
-hdiutil convert "$TMP_DMG" -format UDZO -imagekey zlib-level=9 -o "$DMG"
+mkdir -p "$(dirname "${DMG}")"
+hdiutil convert "${TMP_DMG}" -format UDZO -imagekey zlib-level=9 -o "${DMG}"
-if [ ! $KEEP_TEMP ]; then
+if [ ! ${KEEP_TEMP} ]; then
echo "Cleaning up."
- rm -rf "$TMP_DIR"
+ rm -rf "${TMP_DIR}"
fi
diff --git a/scripts/dmg-resources/DS_Store b/scripts/macos/dmg-resources/DS_Store
similarity index 100%
rename from scripts/dmg-resources/DS_Store
rename to scripts/macos/dmg-resources/DS_Store
diff --git a/scripts/dmg-resources/README.txt b/scripts/macos/dmg-resources/README.txt
similarity index 100%
rename from scripts/dmg-resources/README.txt
rename to scripts/macos/dmg-resources/README.txt
diff --git a/scripts/dmg-resources/VolumeIcon.icns b/scripts/macos/dmg-resources/VolumeIcon.icns
similarity index 100%
rename from scripts/dmg-resources/VolumeIcon.icns
rename to scripts/macos/dmg-resources/VolumeIcon.icns
diff --git a/scripts/dmg-resources/background.png b/scripts/macos/dmg-resources/background.png
similarity index 100%
rename from scripts/dmg-resources/background.png
rename to scripts/macos/dmg-resources/background.png
diff --git a/scripts/macos/python-framework.sh b/scripts/macos/python-framework.sh
new file mode 100755
index 00000000000..e5c7d2ed2f9
--- /dev/null
+++ b/scripts/macos/python-framework.sh
@@ -0,0 +1,260 @@
+#!/usr/bin/env bash
+
+usage() {
+ echo "$0 [ --version VERSION ] FRAMEWORKPATH
+
+Fetch, extract and layout a macOS relocatable Python framework at FRAMEWORKPATH
+
+Options:
+ --version VERSION Python version (default 3.5.3)
+
+Note:
+ Python >= 3.6 comes with a bundled openssl library build that is
+ configured to load certificates from
+ /Library/Frameworks/Python.frameworks/\${pyver}/etc/openssl
+
+ This script will patch python's stdlib ssl.py to add a
+ \${PREFIX}/etc/openssl/cert.pem (where \${PREFIX} is the runtime prefix)
+ certificate store to the default verification chain. However it does not
+ actually supply the file.
+
+Example
+-------
+ $ python-framework.sh ./
+ $ Python.framework/Versions/3.5/bin/python3.5 --version
+ $ python-framework.sh --version 2.7.12 ./2.7
+ $ ./2.7/Python.framework/Versions/2.7/bin/python2.7 --version
+"
+}
+
+
+
+VERSION=3.5.3
+VERBOSE_LEVEL=0
+
+
+verbose() {
+ local level=${1:?}
+ shift 1
+ if [[ ${VERBOSE_LEVEL:-0} -ge ${level} ]]; then
+ echo "$@"
+ fi
+}
+
+python-framework-fetch-pkg() {
+ local cachedir=${1:?}
+ local version=${2:?}
+ local filename=python-${version}-macosx10.6.pkg
+ local url="https://www.python.org/ftp/python/${version}/${filename}"
+ mkdir -p "${cachedir}"
+ if [[ -f "${cachedir}/${filename}" ]]; then
+ verbose 1 "python-${version}-macosx10.6.pkg is present in cache"
+ return 0
+ fi
+ local tmpfile=$(mktemp "${cachedir}/${filename}"-XXXX)
+ cleanup-on-exit() {
+ if [ -f "${tmpfile}" ]; then
+ rm "${tmpfile}"
+ fi
+ }
+
+ (
+ trap cleanup-on-exit EXIT
+ verbose 1 "Fetching ${url}"
+ curl -sSL --fail -o "${tmpfile}" "${url}"
+ mv "${tmpfile}" "${cachedir}/${filename}"
+ )
+}
+
+
+python-framework-extract-pkg() {
+ local targetdir=${1:?}
+ local pkgpath=${2:?}
+ mkdir -p "${targetdir}"/Python.framework
+ verbose 1 "Extracting framework at ${targetdir}/Python.framework"
+ tar -O -xf "${pkgpath}" Python_Framework.pkg/Payload | \
+ tar -x -C "${targetdir}"/Python.framework
+}
+
+
+python-framework-relocate() {
+ local fmkdir=${1:?}
+ if [[ ! ${fmkdir} =~ .*/Python.framework ]]; then
+ echo "${fmkdir} is not a Python.framework" >&2
+ return 1
+ fi
+
+ shopt -s nullglob
+ local versions=( "${fmkdir}"/Versions/?.? )
+ shopt -u nullglob
+
+ if [[ ! ${#versions[*]} == 1 ]]; then
+ echo "Single version framework expected (found: ${versions[@]})"
+ return 2
+ fi
+ local ver_short=${versions##*/}
+ local prefix="${fmkdir}"/Versions/${ver_short}
+ local bindir="${prefix}"/bin
+ local libdir="${prefix}"/lib
+
+ local existingid=$(otool -X -D "${prefix}"/Python | tail -n 1)
+ local anchor="${existingid%%/Python.framework*}"
+
+ if [[ ! ${anchor} =~ /.* ]]; then
+ echo "${anchor} is not an absolute path" 2>&1
+ return 2
+ fi
+
+ chmod +w "${fmkdir}"/Versions/${ver_short}/Python
+ # change main lib's install id
+ install_name_tool \
+ -id @rpath/Python.framework/Versions/${ver_short}/Python \
+ "${fmkdir}"/Versions/${ver_short}/Python
+
+ # Add the containing frameworks path to rpath
+ install_name_tool \
+ -add_rpath @loader_path/../../../ \
+ "${fmkdir}"/Versions/${ver_short}/Python
+
+ # all expected executable binaries
+ local binnames=( python${ver_short} python${ver_short}-32 \
+ pythonw${ver_short} pythonw${ver_short}-32 \
+ python${ver_short}m )
+
+ for binname in "${binnames[@]}";
+ do
+ if [ -f "${bindir}/${binname}" ]; then
+ install_name_tool \
+ -change "${anchor}"/Python.framework/Versions/${ver_short}/Python \
+ "@executable_path/../Python" \
+ "${bindir}/${binname}"
+ fi
+ done
+
+ install_name_tool \
+ -change "${anchor}"/Python.framework/Versions/${ver_short}/Python \
+ "@executable_path/../../../../Python" \
+ "${prefix}"/Resources/Python.app/Contents/MacOS/Python
+
+ for lib in libncursesw libmenuw libformw libpanelw libssl libcrypto;
+ do
+ local libpath="${libdir}"/${lib}.dylib
+ if [[ -f "${libpath}" ]]; then
+ local libid=$(otool -X -D "${libpath}")
+ local libbasename=$(basename "${libid}")
+
+ chmod +w "${libpath}"
+ install_name_tool \
+ -id @rpath/Python.framework/Versions/${ver_short}/lib/${libbasename} \
+ "$libpath"
+
+ local librefs=( $(otool -X -L "${libpath}" | cut -d " " -f 1) )
+ for libref in "${librefs[@]}"
+ do
+ if [[ ${libref} =~ ${anchor}/Python.framework/.* ]]; then
+ local libbasename=$(basename "${libref}")
+ install_name_tool \
+ -change "${libref}" @loader_path/${libbasename} \
+ "${libpath}"
+
+ fi
+ done
+ fi
+ done
+
+ local dylibdir="${libdir}"/python${ver_short}/lib-dynload
+ # _curses.so, _curses_panel.so, readline.so, ...
+ local solibs=( "${dylibdir}"/*.so )
+ for libpath in "${solibs[@]}"
+ do
+ local librefs=( $(otool -X -L "${libpath}" | cut -d " " -f 1) )
+ for libref in "${librefs[@]}"
+ do
+ local strip_prefix="${anchor}"/Python.framework
+ local strip_prefixn=$(( ${#strip_prefix} + 1 ))
+ if [[ ${libref} =~ ${strip_prefix}/.* ]]; then
+ local relpath=$(echo "${libref}" | cut -c $(( ${strip_prefixn} + 1))- )
+ # TODO: should @loader_path be preferred here?
+ install_name_tool \
+ -change "${libref}" \
+ @rpath/Python.framework/"${relpath}" \
+ "${libpath}"
+ fi
+ done
+ done
+
+ # files/modules which reference /Library/Frameworks/Python.framework/
+ # - bin/*
+ # - lib/pkgconfig/*.pc
+ # - lib/python3.5/_sysconfigdata.py
+ # - lib/python3.5/config-3.5m/python-config.py
+
+ sed -i.bck s@prefix=${anchor}'.*'@prefix=\${pcfiledir}/../..@ \
+ "${libdir}"/pkgconfig/python-${ver_short}.pc
+
+ # 3.6.* has bundled libssl with a hardcoded absolute openssl_{cafile,capath}
+ # (need to set SSL_CERT_FILE environment var in all scripts?
+ # or patch ssl.py module to add certs to default verify list?)
+ if [[ ${ver_short#*.} -ge 6 ]]; then
+ patch-ssl "${prefix}"
+ fi
+}
+
+
+# patch python 3.6 to add etc/openssl/cert.pem cert store located relative
+# to the runtime prefix.
+patch-ssl() {
+ local prefix=${1:?}
+ # lib/python relative to prefix
+ local pylibdir=$(
+ cd "${prefix}";
+ shopt -s nullglob;
+ local path=( lib/python?.? )
+ echo "${path:?}"
+ )
+
+ patch "${prefix}/${pylibdir}"/ssl.py - <&2; exit 1;;
+ esac
+done
+
+python-framework-fetch-pkg ~/.cache/pkgs/ ${VERSION}
+python-framework-extract-pkg \
+ "${1:?"FRAMEWORKPATH argument is missing"}" \
+ ~/.cache/pkgs/python-${VERSION}-macosx10.6.pkg
+
+python-framework-relocate "${1:?}"/Python.framework
diff --git a/scripts/macos/requirements.txt b/scripts/macos/requirements.txt
new file mode 100644
index 00000000000..1c59fb16c5c
--- /dev/null
+++ b/scripts/macos/requirements.txt
@@ -0,0 +1,22 @@
+# Fixed requirements file for creating a python environment for
+# macOS Orange.app.
+
+# Prebuild dependencies that themself do not publish wheels (e.g. bottleneck)
+--find-links=https://orange.biolab.si/download/files/wheelhouse
+
+AnyQt==0.0.8
+Bottleneck==1.2.0
+chardet==2.3.0
+dill==0.2.6
+docutils==0.13.1
+joblib==0.11
+keyring==10.3.1
+keyrings.alt==2.2
+numpy==1.12.1
+PyQt5==5.8.2
+pyqtgraph==0.10.0
+scikit-learn==0.18.1
+scipy==0.19.0
+sip==4.19.2
+six==1.10.0
+xlrd==1.0.0
diff --git a/scripts/macos/skeleton.app/Contents/Info.plist.in b/scripts/macos/skeleton.app/Contents/Info.plist.in
new file mode 100644
index 00000000000..a7e392ca966
--- /dev/null
+++ b/scripts/macos/skeleton.app/Contents/Info.plist.in
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleName
+ Orange
+ CFBundleExecutable
+ Orange
+ CFBundleIdentifier
+ si.biolab.orange
+ CFBundleGetInfoString
+ Orange, component-based data mining software
+ CFBundleVersion
+ __VERSION__
+ CFBundleShortVersionString
+ __VERSION__
+ CFBundleIconFile
+ orange.icns
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ Orng
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ Orange Canvas Schema
+ CFBundleTypeExtensions
+
+ ows
+
+ CFBundleTypeIconFile
+ schema.icns
+ CFBundleTypeRole
+ Editor
+ LSHandlerRank
+ Default
+
+
+ CFBundleInfoDictionaryVersion
+ 6.0
+ NSPrincipalClass
+ NSApplication
+ NSHighResolutionCapable
+
+ LSMinimumSystemVersion
+ 10.9.0
+
+
diff --git a/scripts/bundle-lite/Orange.app/Contents/PkgInfo b/scripts/macos/skeleton.app/Contents/PkgInfo
similarity index 100%
rename from scripts/bundle-lite/Orange.app/Contents/PkgInfo
rename to scripts/macos/skeleton.app/Contents/PkgInfo
diff --git a/scripts/bundle-lite/Orange.app/Contents/Resources/orange.icns b/scripts/macos/skeleton.app/Contents/Resources/orange.icns
similarity index 100%
rename from scripts/bundle-lite/Orange.app/Contents/Resources/orange.icns
rename to scripts/macos/skeleton.app/Contents/Resources/orange.icns
diff --git a/scripts/bundle-lite/Orange.app/Contents/Resources/schema.icns b/scripts/macos/skeleton.app/Contents/Resources/schema.icns
similarity index 100%
rename from scripts/bundle-lite/Orange.app/Contents/Resources/schema.icns
rename to scripts/macos/skeleton.app/Contents/Resources/schema.icns