CMakeIngestOSXBundleLibraries.cmake 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. #
  2. # CMakeIngestOSXBundleLibraries.cmake
  3. #
  4. # Only for the Mac build.
  5. #
  6. # Depends on OS tools:
  7. # otool
  8. # install_name_tool
  9. #
  10. # This script ingests libraries and frameworks into an existing .app bundle and
  11. # then uses install_name_tool to fixup the references to the newly embedded
  12. # libraries so that they all refer to each other via "@executable_path."
  13. #
  14. # The main intent (and simplifying assumption used for developing the script)
  15. # is to have a single executable .app bundle that becomes "self-contained" by
  16. # copying all non-system libs that it depends on into itself. The further
  17. # assumption is that all such dependencies are simple .dylib shared library
  18. # files or Mac Framework libraries.
  19. #
  20. # This script can be used as part of the build via ADD_CUSTOM_COMMAND, or used
  21. # only during make install via INSTALL SCRIPT.
  22. #
  23. IF(NOT DEFINED input_file)
  24. MESSAGE(FATAL_ERROR "
  25. ${CMAKE_CURRENT_LIST_FILE}(${CMAKE_CURRENT_LIST_LINE}): error: Variable input_file is not defined.
  26. Use a command line like this to use this script:
  27. cmake \"-Dinput_file=filename\" \"-Dextra_libs=/path/to/lib1;/path/to/lib2\" \"-Dlib_path=/path/to/unqualified/libs\" -P \"${CMAKE_CURRENT_LIST_FILE}\"
  28. 'input_file' should be the main executable inside a Mac bundle directory structure.
  29. For example, use 'bin/paraview.app/Contents/MacOS/paraview' from a ParaView binary dir.
  30. 'extra_libs' should be a semi-colon separated list of full path names to extra libraries
  31. to copy into the bundle that cannot be derived from otool -L output. For example, you may
  32. also want to fixup dynamically loaded plugins from your build tree and copy them into the
  33. bundle.
  34. 'lib_path' should be the path where to find libraries referenced without a path name in
  35. otool -L output.
  36. ")
  37. ENDIF(NOT DEFINED input_file)
  38. SET(eol_char "E")
  39. IF(APPLE)
  40. SET(dep_tool "otool")
  41. SET(dep_cmd_args "-L")
  42. SET(dep_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
  43. ENDIF(APPLE)
  44. MESSAGE("")
  45. MESSAGE("# Script \"${CMAKE_CURRENT_LIST_FILE}\" running...")
  46. MESSAGE("")
  47. MESSAGE("input_file: '${input_file}'")
  48. MESSAGE("extra_libs: '${extra_libs}'")
  49. MESSAGE("lib_path: '${lib_path}'")
  50. MESSAGE("")
  51. GET_FILENAME_COMPONENT(input_file_full "${input_file}" ABSOLUTE)
  52. MESSAGE("input_file_full: '${input_file_full}'")
  53. GET_FILENAME_COMPONENT(bundle "${input_file_full}/../../.." ABSOLUTE)
  54. MESSAGE("bundle: '${bundle}'")
  55. FIND_PROGRAM(dep_cmd ${dep_tool})
  56. MACRO(APPEND_UNIQUE au_list_var au_value)
  57. SET(${au_list_var} ${${au_list_var}} "${au_value}")
  58. ENDMACRO(APPEND_UNIQUE)
  59. MACRO(GATHER_DEPENDENTS gd_target gd_dependents_var)
  60. EXECUTE_PROCESS(
  61. COMMAND ${dep_cmd} ${dep_cmd_args} ${gd_target}
  62. OUTPUT_VARIABLE dep_tool_ov
  63. )
  64. STRING(REGEX REPLACE ";" "\\\\;" dep_candidates "${dep_tool_ov}")
  65. STRING(REGEX REPLACE "\n" "${eol_char};" dep_candidates "${dep_candidates}")
  66. SET(${gd_dependents_var} "")
  67. FOREACH(candidate ${dep_candidates})
  68. IF("${candidate}" MATCHES "${dep_regex}")
  69. STRING(REGEX REPLACE "${dep_regex}" "\\1" raw_item "${candidate}")
  70. STRING(REGEX REPLACE "${dep_regex}" "\\2" raw_compat_version "${candidate}")
  71. STRING(REGEX REPLACE "${dep_regex}" "\\3" raw_current_version "${candidate}")
  72. SET(item "${raw_item}")
  73. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}")
  74. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}")
  75. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}")
  76. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}")
  77. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}")
  78. STRING(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}")
  79. #MESSAGE("${raw_item} - compat ${raw_compat_version} - current ${raw_current_version}")
  80. APPEND_UNIQUE("${gd_dependents_var}" "${item}")
  81. ELSE("${candidate}" MATCHES "${dep_regex}")
  82. IF("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  83. #MESSAGE("info: ignoring target name...")
  84. ELSE("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  85. MESSAGE("error: candidate='${candidate}'")
  86. ENDIF("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  87. ENDIF("${candidate}" MATCHES "${dep_regex}")
  88. ENDFOREACH(candidate)
  89. ENDMACRO(GATHER_DEPENDENTS)
  90. MESSAGE("Gathering dependent libraries for '${input_file_full}'...")
  91. GATHER_DEPENDENTS("${input_file_full}" deps)
  92. MESSAGE("")
  93. # Order lexicographically:
  94. #
  95. LIST(SORT deps)
  96. # Split into separate lists, "system" "embedded" and "nonsystem" libraries.
  97. # System libs are assumed to be available on all target runtime Macs and do not
  98. # need to be copied/fixed-up by this script. Embedded libraries are assumed to
  99. # be in the bundle and fixed-up already. Only non-system, non-embedded libs
  100. # need copying and fixing up...
  101. #
  102. SET(system_deps "")
  103. SET(embedded_deps "")
  104. SET(nonsystem_deps "")
  105. FOREACH(d ${deps})
  106. SET(d_is_embedded_lib 0)
  107. SET(d_is_system_lib 0)
  108. IF("${d}" MATCHES "^(/System/Library|/usr/lib)")
  109. SET(d_is_system_lib 1)
  110. ELSE("${d}" MATCHES "^(/System/Library|/usr/lib)")
  111. IF("${d}" MATCHES "^@executable_path")
  112. SET(d_is_embedded_lib 1)
  113. ENDIF("${d}" MATCHES "^@executable_path")
  114. ENDIF("${d}" MATCHES "^(/System/Library|/usr/lib)")
  115. IF(d_is_system_lib)
  116. SET(system_deps ${system_deps} "${d}")
  117. ELSE(d_is_system_lib)
  118. IF(d_is_embedded_lib)
  119. SET(embedded_deps ${embedded_deps} "${d}")
  120. ELSE(d_is_embedded_lib)
  121. SET(nonsystem_deps ${nonsystem_deps} "${d}")
  122. ENDIF(d_is_embedded_lib)
  123. ENDIF(d_is_system_lib)
  124. ENDFOREACH(d)
  125. MESSAGE("")
  126. MESSAGE("system_deps:")
  127. FOREACH(d ${system_deps})
  128. MESSAGE("${d}")
  129. ENDFOREACH(d ${system_deps})
  130. MESSAGE("")
  131. MESSAGE("embedded_deps:")
  132. FOREACH(d ${embedded_deps})
  133. MESSAGE("${d}")
  134. ENDFOREACH(d ${embedded_deps})
  135. MESSAGE("")
  136. MESSAGE("nonsystem_deps:")
  137. FOREACH(d ${nonsystem_deps})
  138. MESSAGE("${d}")
  139. ENDFOREACH(d ${nonsystem_deps})
  140. MESSAGE("")
  141. MACRO(COPY_LIBRARY_INTO_BUNDLE clib_bundle clib_libsrc clib_dstlibs clib_fixups)
  142. #
  143. # If the source library is a framework, copy just the shared lib bit of the framework
  144. # into the bundle under "${clib_bundle}/Contents/Frameworks" - if it is just a dylib
  145. # copy it into the same directory with the main bundle executable under
  146. # "${clib_bundle}/Contents/MacOS"
  147. #
  148. IF("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  149. GET_FILENAME_COMPONENT(fw_src "${clib_libsrc}" ABSOLUTE)
  150. GET_FILENAME_COMPONENT(fw_srcdir "${clib_libsrc}/../../.." ABSOLUTE)
  151. GET_FILENAME_COMPONENT(fwdirname "${fw_srcdir}" NAME)
  152. STRING(REGEX REPLACE "^(.*)\\.framework$" "\\1" fwname "${fwdirname}")
  153. STRING(REGEX REPLACE "^.*/${fwname}\\.framework/(.*)$" "\\1" fwlibname "${clib_libsrc}")
  154. SET(fw_dstdir "${clib_bundle}/Contents/Frameworks/${fwdirname}")
  155. # MESSAGE("")
  156. # MESSAGE("fwdirname: '${fwdirname}'")
  157. # MESSAGE("fwname: '${fwname}'")
  158. # MESSAGE("fwlibname: '${fwlibname}'")
  159. # MESSAGE("fw_src: '${fw_src}'")
  160. # MESSAGE("fw_srcdir: '${fw_srcdir}'")
  161. # MESSAGE("fw_dstdir: '${fw_dstdir}'")
  162. # MESSAGE("new_name: '@executable_path/../Frameworks/${fwdirname}/${fwlibname}'")
  163. # MESSAGE("")
  164. MESSAGE("Copying ${fw_srcdir} into bundle...")
  165. # This command copies the *entire* framework recursively:
  166. #
  167. # EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy_directory
  168. # "${fw_srcdir}" "${fw_dstdir}"
  169. # )
  170. # This command copies just the main shared lib of the framework:
  171. # (This technique will not work for frameworks that have necessary
  172. # resource or auxiliary files...)
  173. #
  174. EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy
  175. "${fw_src}" "${fw_dstdir}/${fwlibname}"
  176. )
  177. EXECUTE_PROCESS(COMMAND install_name_tool
  178. -id "@executable_path/../Frameworks/${fwdirname}/${fwlibname}"
  179. "${clib_bundle}/Contents/Frameworks/${fwdirname}/${fwlibname}"
  180. )
  181. SET(${clib_dstlibs} ${${clib_dstlibs}}
  182. "${clib_bundle}/Contents/Frameworks/${fwdirname}/${fwlibname}"
  183. )
  184. SET(${clib_fixups} ${${clib_fixups}}
  185. "-change"
  186. "${clib_libsrc}"
  187. "@executable_path/../Frameworks/${fwdirname}/${fwlibname}"
  188. )
  189. ELSE("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  190. IF("${clib_libsrc}" MATCHES "/")
  191. SET(clib_libsrcfull "${clib_libsrc}")
  192. ELSE("${clib_libsrc}" MATCHES "/")
  193. SET(clib_libsrcfull "${lib_path}/${clib_libsrc}")
  194. IF(NOT EXISTS "${clib_libsrcfull}")
  195. MESSAGE(FATAL_ERROR "error: '${clib_libsrcfull}' does not exist...")
  196. ENDIF(NOT EXISTS "${clib_libsrcfull}")
  197. ENDIF("${clib_libsrc}" MATCHES "/")
  198. GET_FILENAME_COMPONENT(dylib_src "${clib_libsrcfull}" ABSOLUTE)
  199. GET_FILENAME_COMPONENT(dylib_name "${dylib_src}" NAME)
  200. SET(dylib_dst "${clib_bundle}/Contents/MacOS/${dylib_name}")
  201. # MESSAGE("dylib_src: ${dylib_src}")
  202. # MESSAGE("dylib_dst: ${dylib_dst}")
  203. # MESSAGE("new_name: '@executable_path/${dylib_name}'")
  204. MESSAGE("Copying ${dylib_src} into bundle...")
  205. EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy
  206. "${dylib_src}" "${dylib_dst}")
  207. EXECUTE_PROCESS(COMMAND install_name_tool
  208. -id "@executable_path/${dylib_name}"
  209. "${dylib_dst}"
  210. )
  211. SET(${clib_dstlibs} ${${clib_dstlibs}}
  212. "${dylib_dst}"
  213. )
  214. SET(${clib_fixups} ${${clib_fixups}}
  215. "-change"
  216. "${clib_libsrc}"
  217. "@executable_path/${dylib_name}"
  218. )
  219. ENDIF("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  220. ENDMACRO(COPY_LIBRARY_INTO_BUNDLE)
  221. # Copy dependent "nonsystem" libraries into the bundle:
  222. #
  223. MESSAGE("Copying dependent libraries into bundle...")
  224. SET(srclibs ${nonsystem_deps} ${extra_libs})
  225. SET(dstlibs "")
  226. SET(fixups "")
  227. FOREACH(d ${srclibs})
  228. COPY_LIBRARY_INTO_BUNDLE("${bundle}" "${d}" dstlibs fixups)
  229. ENDFOREACH(d)
  230. MESSAGE("")
  231. MESSAGE("dstlibs='${dstlibs}'")
  232. MESSAGE("")
  233. MESSAGE("fixups='${fixups}'")
  234. MESSAGE("")
  235. # Fixup references to copied libraries in the main bundle executable and in the
  236. # copied libraries themselves:
  237. #
  238. IF(NOT "${fixups}" STREQUAL "")
  239. MESSAGE("Fixing up references...")
  240. FOREACH(d ${dstlibs} "${input_file_full}")
  241. MESSAGE("fixing up references in: '${d}'")
  242. EXECUTE_PROCESS(COMMAND install_name_tool ${fixups} "${d}")
  243. ENDFOREACH(d)
  244. MESSAGE("")
  245. ENDIF(NOT "${fixups}" STREQUAL "")
  246. # List all references to eyeball them and make sure they look right:
  247. #
  248. MESSAGE("Listing references...")
  249. FOREACH(d ${dstlibs} "${input_file_full}")
  250. EXECUTE_PROCESS(COMMAND otool -L "${d}")
  251. MESSAGE("")
  252. ENDFOREACH(d)
  253. MESSAGE("")
  254. # Output file:
  255. #
  256. GET_FILENAME_COMPONENT(script_name "${CMAKE_CURRENT_LIST_FILE}" NAME)
  257. FILE(WRITE "${input_file_full}_${script_name}" "# Script \"${CMAKE_CURRENT_LIST_FILE}\" completed.\n")
  258. MESSAGE("")
  259. MESSAGE("# Script \"${CMAKE_CURRENT_LIST_FILE}\" completed.")
  260. MESSAGE("")