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