From ccfaa56b3149af9c20572226bcd419bba5672a2e Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Wed, 25 Mar 2015 17:09:50 +1300 Subject: [PATCH] Improves build on MacOS with Homebrew or MacPorts Squashed commit of the following: commit e158a2049b922cead90cee4a9d3814093db8d00d Merge: e7c5c06 d545f5b Author: Ian Rees Date: Wed Mar 25 17:08:56 2015 +1300 Merge branch 'mac-app-bundle' into macports-build-1 commit d545f5b0de0efa6a0fd020ac98bb6809d9254019 Author: Ian Rees Date: Thu Mar 19 22:30:20 2015 +1300 Minor fixes to Mac application bundle creation commit e7c5c0630b47e89fc719259f3d81be724627f915 Author: Ian Rees Date: Thu Mar 19 16:35:34 2015 +1300 Report error if no OpenCasCADe found commit 976b51c13a1619acf66b9d4fad5594fee292aa76 Author: Ian Rees Date: Thu Mar 19 16:30:47 2015 +1300 Detects OCE automatically on Homebrew and MacPorts commit 1cc477f77f388f2ccb26f3884320819f8cb33249 Author: Ian Rees Date: Tue Mar 17 15:00:34 2015 +1300 Find PySide and Shiboken automagically on MacPorts commit 8bf2ebf7397a1c8c4b1b6f1d97e303f335ab47d7 Author: Ian Rees Date: Mon Mar 16 23:05:35 2015 +1300 Finds the Python include dir and library on OSX commit 8bba9b2c78cfe65d7c295c4c99f0a176e1281539 Author: Ian Rees Date: Mon Mar 16 17:31:43 2015 +1300 Detects Python executable in MacPorts or Homebrew commit 349a2e0e5c4d370c331bdb54d80d8f4323db8a6b Author: Ian Rees Date: Mon Mar 16 08:25:16 2015 +1300 Missing correct Python lib is error on cmake-ing commit 1625fe7c1c7fb2d944b04d93be8cf90d5c829be9 Author: Ian Rees Date: Sun Mar 15 21:06:08 2015 +1300 Find PySide's UIC and RCC tools properly on MacPorts --- CMakeLists.txt | 144 +++++++++++++++++- cMake/FindOpenCasCade.cmake | 11 +- cMake/FindPySideTools.cmake | 11 +- src/MacAppBundle/CMakeLists.txt | 7 +- .../FreeCAD.app/Contents/Info.plist | 4 +- src/Tools/MakeMacBundleRelocatable.py | 49 +++--- 6 files changed, 180 insertions(+), 46 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 83d2a6c0e..337fd7af8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,11 +126,21 @@ if(APPLE) if(FREECAD_CREATE_MAC_APP) install( - DIRECTORY ${CMAKE_SOURCE_DIR}/src/MacAppBundle/FreeCAD.app - DESTINATION ${CMAKE_INSTALL_PREFIX} + DIRECTORY ${CMAKE_SOURCE_DIR}/src/MacAppBundle/FreeCAD.app/ + DESTINATION ${CMAKE_INSTALL_PREFIX}/${PACKAGE_NAME}.app ) - set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/FreeCAD.app/Contents) - SET(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX}/lib) + + # It should be safe to assume we've got sed on OSX... + install(CODE " + execute_process(COMMAND + sed -i \"\" -e s/VERSION_STRING_FROM_CMAKE/${PACKAGE_VERSION}/ + -e s/NAME_STRING_FROM_CMAKE/${PACKAGE_NAME}/ + ${CMAKE_INSTALL_PREFIX}/${PACKAGE_NAME}.app/Contents/Info.plist) + ") + + set(CMAKE_INSTALL_PREFIX + ${CMAKE_INSTALL_PREFIX}/${PACKAGE_NAME}.app/Contents) + set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX}/lib) endif(FREECAD_CREATE_MAC_APP) endif(APPLE) @@ -419,9 +429,122 @@ if(NOT FREECAD_LIBPACK_USE OR FREECAD_LIBPACK_CHECKFILE_CLBUNDLER) # -------------------------------- Python -------------------------------- +#http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=677598 +# Acceptable versions of Python +set(Python_ADDITIONAL_VERSIONS "2.3" "2.4" "2.5" "2.6" "2.7" "2.8" "2.9") + +# For building on OS X +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + # If the user doesn't tell us which package manager they're using + if(NOT DEFINED MACPORTS_PREFIX AND NOT DEFINED HOMEBREW_PREFIX) + + # Try to find MacPorts path + find_program(MACPORTS_EXECUTABLE port) + if(EXISTS ${MACPORTS_EXECUTABLE}) + string(REPLACE "/bin/port" "" + MACPORTS_PREFIX ${MACPORTS_EXECUTABLE}) + message(STATUS "Detected MacPorts install at ${MACPORTS_PREFIX}") + endif(EXISTS ${MACPORTS_EXECUTABLE}) + + # Try to find Homebrew path + find_program(HOMEBREW_EXECUTABLE brew) + if(EXISTS ${HOMEBREW_EXECUTABLE}) + set(HOMEBREW_PREFIX "/usr/local") + message(STATUS "Detected Homebrew install at ${HOMEBREW_PREFIX}") + endif() + + endif(NOT DEFINED MACPORTS_PREFIX AND NOT DEFINED HOMEBREW_PREFIX) + + # In case someone tries to shoot themselves in the foot + if(DEFINED MACPORTS_PREFIX AND DEFINED HOMEBREW_PREFIX) + message(SEND_ERROR + "Multiple package management systems detected - ") + message(SEND_ERROR + "define either MACPORTS_PREFIX or HOMEBREW_PREFIX") + + # No package manager + elseif(NOT DEFINED MACPORTS_PREFIX AND NOT DEFINED HOMEBREW_PREFIX) + message(SEND_ERROR + "No package manager detected - install MacPorts or Homebrew") + + # The hopefully-normal case - one package manager identified + else(DEFINED MACPORTS_PREFIX AND DEFINED HOMEBREW_PREFIX) + + # Construct a list like python;python2.9;python2.8;... + set(Python_ADDITIONAL_VERSIONS_REV ${Python_ADDITIONAL_VERSIONS}) + list(REVERSE Python_ADDITIONAL_VERSIONS_REV) + set(_PYTHON_NAMES "python") + foreach(_PYTHON_VERSION IN LISTS Python_ADDITIONAL_VERSIONS_REV) + list(APPEND _PYTHON_NAMES "python${_PYTHON_VERSION}") + endforeach(_PYTHON_VERSION) + + # Find python in the package management systems, using names in that + # list in decreasing priority. Note that a manually specified + # PYTHON_EXECUTABLE still has prescedence over this. + find_program(PYTHON_EXECUTABLE + NAMES ${_PYTHON_NAMES} + PATHS ${MACPORTS_PREFIX} ${HOMEBREW_PREFIX} + PATH_SUFFIXES /bin + NO_DEFAULT_PATH) + + endif(DEFINED MACPORTS_PREFIX AND DEFINED HOMEBREW_PREFIX) + + # Warn user if we still only have the system Python + string(FIND ${PYTHON_EXECUTABLE} "/usr/bin/python" _FIND_SYS_PYTHON) + if(_FIND_SYS_PYTHON EQUAL 0) + message(SEND_ERROR + "Only found the stock Python, that's probably bad.") + endif(_FIND_SYS_PYTHON EQUAL 0) + + # Ask Python to tell us it's include directory, if nobody else has + if(NOT DEFINED PYTHON_INCLUDE_DIR) + execute_process(COMMAND ${PYTHON_EXECUTABLE} -c + "from distutils.sysconfig import get_python_inc;print get_python_inc()" + OUTPUT_VARIABLE PYTHON_INCLUDE_DIR + RESULT_VARIABLE PYTHON_INCLUDE_DIR_RESULT + ERROR_QUIET) + if(NOT PYTHON_INCLUDE_DIR_RESULT MATCHES 0) + message(SEND_ERROR "Failed to determine PYTHON_INCLUDE_DIR") + endif(NOT PYTHON_INCLUDE_DIR_RESULT MATCHES 0) + endif(NOT DEFINED PYTHON_INCLUDE_DIR) + + # Similar for the Python library - there must be an easier way... + if(NOT DEFINED PYTHON_LIBRARY) + # Get the library path + execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c + "from distutils import sysconfig;print sysconfig.get_config_var('LIBDIR')" + OUTPUT_VARIABLE PYTHON_LIBRARY_DIR + RESULT_VARIABLE PYTHON_LIBRARY_DIR_RESULT + ERROR_QUIET) + string(STRIP ${PYTHON_LIBRARY_DIR} PYTHON_LIBRARY_DIR) + if(NOT PYTHON_LIBRARY_DIR_RESULT MATCHES 0) + message(SEND_ERROR "Failed to determine PYTHON_LIBRARY") + endif(NOT PYTHON_LIBRARY_DIR_RESULT MATCHES 0) + + # Get library filename - might not be safe to assume .dylib extension? + execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c + "import sys;print 'libpython%d.%d.dylib'%sys.version_info[0:2]" + OUTPUT_VARIABLE PYTHON_LIBRARY_FILE + RESULT_VARIABLE PYTHON_LIBRARY_FILE_RESULT + ERROR_QUIET) + string(STRIP ${PYTHON_LIBRARY_FILE} PYTHON_LIBRARY_FILE) + if(NOT PYTHON_LIBRARY_FILE_RESULT MATCHES 0) + message(SEND_ERROR "Failed to determine PYTHON_LIBRARY") + endif(NOT PYTHON_LIBRARY_FILE_RESULT MATCHES 0) + + set(PYTHON_LIBRARY "${PYTHON_LIBRARY_DIR}/${PYTHON_LIBRARY_FILE}") + + else(NOT DEFINED PYTHON_LIBRARY) + # Used on MacPorts systems for finding Shiboken and PySide + # TODO: When we start requiring minimum CMake version above + # 2.8.11, change PATH below to DIRECTORY + get_filename_component(PYTHON_LIBRARY_DIR ${PYTHON_LIBRARY} PATH) + endif(NOT DEFINED PYTHON_LIBRARY) + + +endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - #http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=677598 - set(Python_ADDITIONAL_VERSIONS "2.3" "2.4" "2.5" "2.6" "2.7" "2.8" "2.9") find_package(PythonInterp REQUIRED) IF (NOT DEFINED PYTHON_VERSION_STRING) find_package(PythonLibs REQUIRED) @@ -429,8 +552,9 @@ if(NOT FREECAD_LIBPACK_USE OR FREECAD_LIBPACK_CHECKFILE_CLBUNDLER) find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT) ENDIF(NOT DEFINED PYTHON_VERSION_STRING) + IF(NOT PYTHONLIBS_FOUND) - MESSAGE("Python not found, install python!") + MESSAGE(FATAL_ERROR "Python not found, install python!") ENDIF(NOT PYTHONLIBS_FOUND) # -------------------------------- pcl ---------------------------------- @@ -599,6 +723,12 @@ if(NOT FREECAD_LIBPACK_USE OR FREECAD_LIBPACK_CHECKFILE_CLBUNDLER) # set(PYTHON_SUFFIX -python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}) SET(PYTHON_SUFFIX -python2.7) # for shiboken SET(PYTHON_BASENAME -python2.7) # for PySide + + if(DEFINED MACPORTS_PREFIX) + find_package(Shiboken REQUIRED HINTS "${PYTHON_LIBRARY_DIR}/cmake") + find_package(PySide REQUIRED HINTS "${PYTHON_LIBRARY_DIR}/cmake") + endif(DEFINED MACPORTS_PREFIX) + find_package(Shiboken REQUIRED) find_package(PySide REQUIRED) find_package(PySideTools REQUIRED) # Pyside utilities (pyside-uic & pyside-rcc) diff --git a/cMake/FindOpenCasCade.cmake b/cMake/FindOpenCasCade.cmake index 4590d674d..6319fbf6a 100644 --- a/cMake/FindOpenCasCade.cmake +++ b/cMake/FindOpenCasCade.cmake @@ -9,7 +9,14 @@ # First try to find OpenCASCADE Community Edition if(NOT DEFINED OCE_DIR) - if(UNIX) + # Check for OSX needs to come first because UNIX evaluates to true on OSX + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + if(DEFINED MACPORTS_PREFIX) + find_package(OCE HINTS ${MACPORTS_PREFIX}/Library/Frameworks) + elseif(DEFINED HOMEBREW_PREFIX) + find_package(OCE HINTS ${HOMEBREW_PREFIX}/Cellar/oce/*) + endif() + elseif(UNIX) set(OCE_DIR "/usr/local/share/cmake/") elseif(WIN32) set(OCE_DIR "c:/OCE-0.4.0/share/cmake") @@ -126,5 +133,5 @@ if(OCC_FOUND) message(STATUS "-- OCE/OpenCASCADE include directory: ${OCC_INCLUDE_DIR}") message(STATUS "-- OCE/OpenCASCADE shared libraries directory: ${OCC_LIBRARY_DIR}") else(OCC_FOUND) - message("Neither OpenCASCADE Community Edition nor OpenCasCade were found: will not build CAD modules!") + message(SEND_ERROR "Neither OpenCASCADE Community Edition nor OpenCasCade were found: will not build CAD modules!") endif(OCC_FOUND) diff --git a/cMake/FindPySideTools.cmake b/cMake/FindPySideTools.cmake index 0a2caae5d..053cb93e0 100644 --- a/cMake/FindPySideTools.cmake +++ b/cMake/FindPySideTools.cmake @@ -14,17 +14,14 @@ IF(PYSIDEUIC4BINARY AND PYSIDERCC4BINARY) set(PYSIDE_TOOLS_FOUND_QUIETLY TRUE) ENDIF(PYSIDEUIC4BINARY AND PYSIDERCC4BINARY) -if(WIN32 OR APPLE) +if(WIN32 OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") #pyside tools are often in same location as python interpreter get_filename_component(PYTHON_BIN_DIR ${PYTHON_EXECUTABLE} PATH) set(PYSIDE_BIN_DIR ${PYTHON_BIN_DIR}) -endif(WIN32 OR APPLE) +endif(WIN32 OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") -FIND_PROGRAM(PYSIDEUIC4BINARY pyside-uic HINTS ${PYSIDE_BIN_DIR}) -FIND_PROGRAM(PYSIDERCC4BINARY pyside-rcc HINTS ${PYSIDE_BIN_DIR}) - -#message(STATUS "PYSIDEUIC4BINARY ${PYSIDEUIC4BINARY}" ) -#message(STATUS "PYSIDERCC4BINARY ${PYSIDERCC4BINARY}" ) +FIND_PROGRAM(PYSIDEUIC4BINARY NAMES pyside-uic pyside-uic-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} HINTS ${PYSIDE_BIN_DIR}) +FIND_PROGRAM(PYSIDERCC4BINARY NAMES pyside-rcc pyside-rcc-${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} HINTS ${PYSIDE_BIN_DIR}) MACRO(PYSIDE_WRAP_UI outfiles) FOREACH(it ${ARGN}) diff --git a/src/MacAppBundle/CMakeLists.txt b/src/MacAppBundle/CMakeLists.txt index adaa64774..d451dca5f 100644 --- a/src/MacAppBundle/CMakeLists.txt +++ b/src/MacAppBundle/CMakeLists.txt @@ -21,8 +21,11 @@ get_filename_component(APP_PATH ${CMAKE_INSTALL_PREFIX} PATH) install(CODE "message(STATUS \"Making bundle relocatable...\") + # The top-level CMakeLists.txt should prevent multiple package manager + # prefixes from being set, so the lib path will resolve correctly... execute_process( - COMMAND ${PYTHON_EXECUTABLE} - ${CMAKE_SOURCE_DIR}/src/Tools/MakeMacBundleRelocatable.py ${APP_PATH} + COMMAND ${PYTHON_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/src/Tools/MakeMacBundleRelocatable.py + ${APP_PATH} ${HOMEBREW_PREFIX}${MACPORTS_PREFIX}/lib )" ) diff --git a/src/MacAppBundle/FreeCAD.app/Contents/Info.plist b/src/MacAppBundle/FreeCAD.app/Contents/Info.plist index 747d3002c..061b16d6c 100644 --- a/src/MacAppBundle/FreeCAD.app/Contents/Info.plist +++ b/src/MacAppBundle/FreeCAD.app/Contents/Info.plist @@ -17,7 +17,7 @@ CFBundleLongVersionString CFBundleName - FreeCAD + NAME_STRING_FROM_CMAKE CFBundlePackageType APPL CFBundleShortVersionString @@ -25,7 +25,7 @@ CFBundleSignature ???? CFBundleVersion - 0.14-dev + VERSION_STRING_FROM_CMAKE CSResourcesFileMapped NSHumanReadableCopyright diff --git a/src/Tools/MakeMacBundleRelocatable.py b/src/Tools/MakeMacBundleRelocatable.py index de863c901..d44a94963 100644 --- a/src/Tools/MakeMacBundleRelocatable.py +++ b/src/Tools/MakeMacBundleRelocatable.py @@ -3,7 +3,7 @@ import sys import subprocess import pprint -SYS_PATHS = ["/System/","/usr/lib/"] +SYS_PATHS = ["/System/", "/usr/lib/"] class LibraryNotFound(Exception): pass @@ -64,7 +64,7 @@ class DepsGraph: operation(self, self.graph[node_key], *op_args) def is_macho(path): - output = subprocess.check_output(["file",path]) + output = subprocess.check_output(["file", path]) if output.count("Mach-O") != 0: return True @@ -134,7 +134,7 @@ def create_dep_nodes(install_names, search_paths): path = install_path if not path: - raise LibraryNotFound("{0} not found in given paths".format(lib_name)) + raise LibraryNotFound(lib_name + "not found in given paths") nodes.append(Node(lib_name, path)) @@ -155,10 +155,6 @@ def should_visit(prefix, path_filters, path): #we only want to use filters if they have the same parent as path for rel_pf in path_filters: pf = os.path.join(prefix, rel_pf) - #print(path) - #print(pf) - #print(os.path.split(pf)[0] == os.path.split(path)[0]) - #print('----') if os.path.split(pf)[0] == os.path.split(path)[0]: filters.append(pf) if not filters: @@ -189,7 +185,7 @@ def build_deps_graph(graph, bundle_path, dirs_filter=None, search_paths=[]): for root, dirs, files in os.walk(bundle_path): if dirs_filter != None: - dirs[:] = [d for d in dirs if should_visit(bundle_path, dirs_filter, + dirs[:] = [d for d in dirs if should_visit(bundle_path, dirs_filter, os.path.join(root, d))] s_paths.insert(0, root) @@ -197,7 +193,8 @@ def build_deps_graph(graph, bundle_path, dirs_filter=None, search_paths=[]): for f in files: fpath = os.path.join(root, f) ext = os.path.splitext(f)[1] - if (ext == "" and is_macho(fpath)) or ext == ".so" or ext == ".dylib": + if ( (ext == "" and is_macho(fpath)) or + ext == ".so" or ext == ".dylib" ): visited[fpath] = False stack = [] @@ -223,8 +220,6 @@ def build_deps_graph(graph, bundle_path, dirs_filter=None, search_paths=[]): if not visited[dk]: stack.append(dk) - - def in_bundle(lib, bundle_path): if lib.startswith(bundle_path): return True @@ -232,12 +227,13 @@ def in_bundle(lib, bundle_path): def copy_into_bundle(graph, node, bundle_path): if not in_bundle(node.path, bundle_path): - subprocess.check_call(["cp","-L",os.path.join(node.path,node.name), - os.path.join(bundle_path,"lib",node.name)]) - node.path = os.path.join(bundle_path,"lib") - - #fix permissions - subprocess.check_call(["chmod", "a+w", os.path.join(bundle_path,"lib",node.name)]) + subprocess.check_call(["cp", "-L", os.path.join(node.path, node.name), + os.path.join(bundle_path, "lib", node.name)]) + node.path = os.path.join(bundle_path, "lib") + + #fix permissions + subprocess.check_call(["chmod", "a+w", os.path.join(bundle_path, + "lib", node.name)]) def add_rpaths(graph, node, bundle_path): if node.children: @@ -249,11 +245,12 @@ def add_rpaths(graph, node, bundle_path): for install_name in install_names: name = os.path.basename(install_name) #change install names to use rpaths - subprocess.check_call(["install_name_tool","-change", + subprocess.check_call(["install_name_tool", "-change", install_name, "@rpath/" + name, lib]) dep_node = node.children[node.children.index(name)] - rel_path = os.path.relpath(graph.get_node(dep_node).path, node.path) + rel_path = os.path.relpath(graph.get_node(dep_node).path, + node.path) rpath = "" if rel_path == ".": rpath = "@loader_path/" @@ -264,21 +261,21 @@ def add_rpaths(graph, node, bundle_path): for path in rpaths: subprocess.call(["install_name_tool", "-add_rpath", path, lib]) -def print_node(graph, node): - print(os.path.join(node.path, node.name)) - def main(): + if len(sys.argv) < 2: + print "Usage " + sys.argv[0] + " path [additional search paths]" + quit() + path = sys.argv[1] bundle_path = os.path.abspath(os.path.join(path, "Contents")) graph = DepsGraph() - dir_filter = ["bin","lib", "Mod","Mod/PartDesign", + dir_filter = ["bin", "lib", "Mod", "Mod/PartDesign", "lib/python2.7/site-packages", "lib/python2.7/lib-dynload"] - search_paths = [bundle_path + "/lib", "/usr/local/lib", "/usr/local/Cellar/freetype/2.5.4/lib"] - + search_paths = [bundle_path + "/lib"] + sys.argv[2:] + build_deps_graph(graph, bundle_path, dir_filter, search_paths) - #graph.visit(print_node) graph.visit(copy_into_bundle, [bundle_path]) graph.visit(add_rpaths, [bundle_path])