Commit 9f7e22ec authored by Gigg, Martyn Anthony's avatar Gigg, Martyn Anthony Committed by Peterson, Peter
Browse files

Fix macOS packaging on modern setuptools/homebrew setup

  - easy_install is no longer distributed
  - we pass in the Qt install root rather than guessing it

We also pin the pip installed packages to those that were
used to build the package rather than maintaining a separate
list of version numbers. This particularly ensures that the
versions of numpy match appropriately and will ensure
more reproducibility in the package
parent 678efb91
......@@ -92,7 +92,10 @@ function(add_python_package pkg_name)
# --install-lib=lib removes any of the platform/distribution specific install
# directories so we can have a flat structure
install(
CODE "execute_process(COMMAND ${Python_EXECUTABLE} ${_setup_py} install -O1 --single-version-externally-managed --root=${_setup_py_build_root}/install --install-scripts=bin --install-lib=lib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})"
CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E env MANTID_VERSION_STR=${_version_str} \
${Python_EXECUTABLE} ${_setup_py} install -O1 --single-version-externally-managed \
--root=${_setup_py_build_root}/install --install-scripts=bin --install-lib=lib \
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})"
)
# Registers the "installed" components with CMake so it will carry them over
......
......@@ -19,7 +19,6 @@ AT_RPATH_TAG = '@rpath'
# Collection modules to copy from system installation
# Required to install other packages with pip
BUNDLED_PY_MODULES_COMMON = [
'easy_install.py',
'pip',
'pip*.*-info',
'pkg_resources',
......@@ -43,28 +42,48 @@ BUNDLED_PY_MODULES_MANTIDPLOT = [
].freeze
BUNDLED_PY_MODULES_WORKBENCH = [
'PyQt5/__init__.py',
'PyQt5/Qt.so',
'PyQt5/QtCore.so',
'PyQt5/QtGui.so',
'PyQt5/QtOpenGL.so',
'PyQt5/QtPrintSupport.so',
'PyQt5/QtSql.so',
'PyQt5/QtSvg.so',
'PyQt5/QtTest.so',
'PyQt5/QtWidgets.so',
'PyQt5/QtXml.so',
'PyQt5/sip.so',
'PyQt5/Qt.*so',
'PyQt5/QtCore.*so',
'PyQt5/QtGui.*so',
'PyQt5/QtOpenGL.*so',
'PyQt5/QtPrintSupport.*so',
'PyQt5/QtSql.*so',
'PyQt5/QtSvg.*so',
'PyQt5/QtTest.*so',
'PyQt5/QtWidgets.*so',
'PyQt5/QtXml.*so',
'PyQt5/sip.*so',
'PyQt5/uic',
].freeze
REQUIREMENTS_FILE = Pathname.new(__dir__) + 'requirements.txt'
REQUIREMENTS_WORKBENCH_FILE = Pathname.new(__dir__) + 'requirements-workbench.txt'
# Python requirements. Versions hard pinned to those
# used by the host at install time
REQUIREMENTS=[
"h5py",
"ipython",
"ipykernel",
"matplotlib",
"pycifrw",
"PyYAML",
"pyzmq",
"qtconsole",
"qtpy",
"mock",
"notebook",
"numpy",
"psutil",
"requests",
"scipy",
"six",
"sphinx",
"sphinx_bootstrap_theme",
"toml"
].freeze
SITECUSTOMIZE_FILE = Pathname.new(__dir__) + 'sitecustomize.py'
DEBUG = 1
FRAMEWORK_IDENTIFIER = '.framework'
HOMEBREW_PREFIX = '/usr/local'
MANTID_PY_SO = ['_api.so', '_geometry.so', '_kernel.so'].freeze
QT4_PLUGINS_DIR = Pathname.new('/usr/local/opt/qt@4/lib/qt4/plugins')
QT5_PLUGINS_DIR = Pathname.new('/usr/local/opt/qt/plugins')
QT_PLUGINS_COMMON = ['imageformats', 'sqldrivers', 'iconengines'].freeze
QT_PLUGINS_BLACKLIST = ['libqsqlpsql.dylib'].freeze
QT_CONF = '[Paths]
......@@ -122,11 +141,11 @@ end
# +destination+:: Destination directory for bundle
# +host_python_exe+:: Executable of Python bundle to copy over
# +bundled_packages+:: A list of packages that should be bundled
# +requirements_files+:: A list of requirements files to install additional packages
# +requirements+:: A list of Python requirements to install additional packages
# returns the bundle site packages directory
def deploy_python_framework(destination, host_python_exe,
bundled_packages,
requirements_files)
requirements)
host_py_home = host_python_exe.realpath.parent.parent
py_ver = host_py_home.basename
bundle_python_framework = destination + 'Frameworks/Python.framework'
......@@ -194,11 +213,8 @@ def deploy_python_framework(destination, host_python_exe,
make_writable(bundle_site_packages)
# remove distutils.cfg so pip paths are computed relative to the sys.prefix
FileUtils.rm Pathname.new("#{bundle_py_home}/lib/python#{py_ver}/distutils/distutils.cfg")
requirements_files.each do |requirements|
stdout = execute("#{bundle_py_home}/bin/python -m pip install -r #{requirements}")
debug(stdout)
end
install_requirements(requirements, "#{bundle_py_home}/bin/python", host_python_exe)
# fix mpl_toolkit if it is missing __init__
mpltoolkit_init =
FileUtils.touch "#{bundle_site_packages}/mpl_toolkits/__init__.py"
......@@ -210,6 +226,20 @@ def deploy_python_framework(destination, host_python_exe,
bundle_site_packages
end
# Installs the list of Python requirements into the package bundle
# Params:
# +requirements+:: A list of string requirements
# +bundle_py_exe+:: Path to the bundled Python exe
# +host_py_exe+:: Path to the host Python exe
def install_requirements(requirements, bundle_py_exe, host_py_exe)
requirements.each do |requirement|
pkg_info = execute("#{host_py_exe} -m pip show #{requirement}")
version = /Version:\s+([0-9]+\.[0-9]+\.[0-9]+)/.match(pkg_info)[1]
stdout = execute("#{bundle_py_exe} -m pip install --disable-pip-version-check #{requirement}==#{version}")
debug(stdout)
end
end
# Copies, recursively, the selected list of packages from the
# src to the destination. The destination must already exist
# Params:
......@@ -546,16 +576,19 @@ end
#---------------------------------------------------------
# Main script
#---------------------------------------------------------
if (ARGV.length != 2)
puts 'Usage: make_package bundle-path python-exe'
if (ARGV.length != 3)
puts 'Usage: make_package bundle-path python-exe qt-prefix'
puts ' - bundle-path: Path of bundle to fix'
puts ' - python-exe: Path to Python executable to bundle. The whole Python.framework is bundled.'
puts ' - qt-prefix: Root directory of the Qt installation'
exit 1
end
# Host paths
host_python_exe = Pathname.new(ARGV[1])
fatal("Python executable #{python_exe} not found") unless host_python_exe.exist?
host_qt_prefix = Pathname.new(ARGV[2])
fatal("Qt prefix #{host_qt_prefix} not found") unless host_qt_prefix.exist?
# Bundle paths
bundle_path = Pathname.new(ARGV[0])
......@@ -572,13 +605,11 @@ python_version_minor = python_version_full[1]
so_suffix = ''
bundled_packages = BUNDLED_PY_MODULES_COMMON.map { |s| s % "cpython-%d%d%s-darwin" % [python_version_major, python_version_minor, so_suffix] }
requirements_files = [REQUIREMENTS_FILE]
# check we have a known bundle
if bundle_path.to_s.include?('MantidWorkbench')
bundled_packages += BUNDLED_PY_MODULES_WORKBENCH
requirements_files << REQUIREMENTS_WORKBENCH_FILE
bundled_qt_plugins = QT_PLUGINS_COMMON + ['platforms', 'printsupport', 'styles']
host_qt_plugins_dir = QT5_PLUGINS_DIR
host_qt_plugins_dir = host_qt_prefix + 'plugins'
executables << "#{contents_macos}/#{bundle_path.basename.to_s.split('.')[0]}"
elsif bundle_path.to_s.include?('MantidPlot')
bundled_packages += BUNDLED_PY_MODULES_MANTIDPLOT
......@@ -592,7 +623,7 @@ end
# We start with the assumption CMake has installed all required target libraries/executables
# into the bundle and the main layout exists.
bundle_py_site_packages = deploy_python_framework(contents, host_python_exe,
bundled_packages, requirements_files)
bundled_packages, REQUIREMENTS)
install_qt_plugins(bundle_path, bundled_qt_plugins, host_qt_plugins_dir,
QT_PLUGINS_BLACKLIST)
......
h5py==2.10.0
ipython==7.14.0
matplotlib==3.1.3
pycifrw==4.4.1
PyYAML==5.4
pyzmq==20.0.0
qtconsole==4.7.3
qtpy==1.9.0
mock==4.0.2
notebook==6.1.5
numpy==1.18.4
requests==2.23.0
scipy==1.6.2
six==1.14.0
sphinx==1.6.7
sphinx_bootstrap_theme==0.7.1
toml==0.10.0
......@@ -126,17 +126,21 @@ if(APPLE)
configure_file ( ${CMAKE_CURRENT_SOURCE_DIR}/../../../installers/MacInstaller/Info.plist.in
${CMAKE_CURRENT_BINARY_DIR}/Info.plist
@ONLY )
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/Info.plist DESTINATION ${WORKBENCH_BUNDLE} )
install (CODE "
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/installers/MacInstaller/make_package.rb
\${CMAKE_INSTALL_PREFIX}/${WORKBENCH_APP} ${Python_EXECUTABLE}
RESULT_VARIABLE install_name_tool_result OUTPUT_VARIABLE _out ERROR_VARIABLE _out COMMAND_ECHO STDOUT)
if(NOT install_name_tool_result EQUAL 0)
message(\"\${_out}\")
message(FATAL_ERROR \"Package script failed!!!\n\")
endif()
")
# Determine Qt install prefix
set(_qt_install_prefix ${Qt5Core_DIR})
foreach(_i RANGE 2)
get_filename_component(_qt_install_prefix ${_qt_install_prefix} DIRECTORY)
endforeach()
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/Info.plist DESTINATION ${WORKBENCH_BUNDLE} )
install (CODE "
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/installers/MacInstaller/make_package.rb
\${CMAKE_INSTALL_PREFIX}/${WORKBENCH_APP} ${Python_EXECUTABLE} ${_qt_install_prefix}
RESULT_VARIABLE install_name_tool_result OUTPUT_VARIABLE _out ERROR_VARIABLE _out COMMAND_ECHO STDOUT)
if(NOT install_name_tool_result EQUAL 0)
message(\"\${_out}\")
message(FATAL_ERROR \"Package script failed!!!\n\")
endif()
")
endif()
# Testing
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment