CMakeIngestOSXBundleLibraries.cmake 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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. message("ingest ${input_file}")
  39. set(eol_char "E")
  40. if(APPLE)
  41. set(dep_tool "otool")
  42. set(dep_cmd_args "-L")
  43. set(dep_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
  44. endif(APPLE)
  45. message("")
  46. message("# Script \"${CMAKE_CURRENT_LIST_FILE}\" running...")
  47. message("")
  48. message("input_file: '${input_file}'")
  49. message("extra_libs: '${extra_libs}'")
  50. message("lib_path: '${lib_path}'")
  51. message("")
  52. get_filename_component(input_file_full "${input_file}" ABSOLUTE)
  53. message("input_file_full: '${input_file_full}'")
  54. get_filename_component(bundle "${input_file_full}/../../.." ABSOLUTE)
  55. message("bundle: '${bundle}'")
  56. find_program(dep_cmd ${dep_tool})
  57. # find the full path to the framework in path set the result
  58. # in pathout
  59. macro(find_framework_full_path path pathout)
  60. set(${pathout} "${path}")
  61. if(NOT EXISTS "${path}")
  62. set(FRAMEWORK_SEARCH "/Library/Frameworks"
  63. "/System/Library/Frameworks" )
  64. set(__FOUND FALSE)
  65. foreach(f ${FRAMEWORK_SEARCH})
  66. set(newd "${f}/${path}")
  67. if(EXISTS "${newd}" AND NOT __FOUND)
  68. set(${pathout} "${newd}")
  69. set(__FOUND TRUE)
  70. endif(EXISTS "${newd}" AND NOT __FOUND)
  71. endforeach(f)
  72. endif(NOT EXISTS "${path}")
  73. endmacro(find_framework_full_path)
  74. macro(append_unique au_list_var au_value)
  75. set(${au_list_var} ${${au_list_var}} "${au_value}")
  76. endmacro(append_unique)
  77. macro(gather_dependents gd_target gd_dependents_var)
  78. execute_process(
  79. COMMAND ${dep_cmd} ${dep_cmd_args} ${gd_target}
  80. OUTPUT_VARIABLE dep_tool_ov
  81. )
  82. string(REGEX REPLACE ";" "\\\\;" dep_candidates "${dep_tool_ov}")
  83. string(REGEX REPLACE "\n" "${eol_char};" dep_candidates "${dep_candidates}")
  84. set(${gd_dependents_var} "")
  85. foreach(candidate ${dep_candidates})
  86. if("${candidate}" MATCHES "${dep_regex}")
  87. string(REGEX REPLACE "${dep_regex}" "\\1" raw_item "${candidate}")
  88. string(REGEX REPLACE "${dep_regex}" "\\2" raw_compat_version "${candidate}")
  89. string(REGEX REPLACE "${dep_regex}" "\\3" raw_current_version "${candidate}")
  90. set(item "${raw_item}")
  91. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}")
  92. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}")
  93. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}")
  94. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}")
  95. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}")
  96. string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}")
  97. #message("${raw_item} - compat ${raw_compat_version} - current ${raw_current_version}")
  98. append_unique("${gd_dependents_var}" "${item}")
  99. else("${candidate}" MATCHES "${dep_regex}")
  100. if("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  101. #message("info: ignoring target name...")
  102. else("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  103. message("error: candidate='${candidate}'")
  104. endif("${candidate}" STREQUAL "${gd_target}:${eol_char}")
  105. endif("${candidate}" MATCHES "${dep_regex}")
  106. endforeach(candidate)
  107. endmacro(gather_dependents)
  108. message("Gathering dependent libraries for '${input_file_full}'...")
  109. gather_dependents("${input_file_full}" deps)
  110. message("")
  111. # Order lexicographically:
  112. #
  113. list(SORT deps)
  114. # Split into separate lists, "system" "embedded" and "nonsystem" libraries.
  115. # System libs are assumed to be available on all target runtime Macs and do not
  116. # need to be copied/fixed-up by this script. Embedded libraries are assumed to
  117. # be in the bundle and fixed-up already. Only non-system, non-embedded libs
  118. # need copying and fixing up...
  119. #
  120. set(system_deps "")
  121. set(embedded_deps "")
  122. set(nonsystem_deps "")
  123. foreach(d ${deps})
  124. set(d_is_embedded_lib 0)
  125. set(d_is_system_lib 0)
  126. if("${d}" MATCHES "^(/System/Library|/usr/lib)")
  127. set(d_is_system_lib 1)
  128. else("${d}" MATCHES "^(/System/Library|/usr/lib)")
  129. if("${d}" MATCHES "^@executable_path")
  130. set(d_is_embedded_lib 1)
  131. endif("${d}" MATCHES "^@executable_path")
  132. endif("${d}" MATCHES "^(/System/Library|/usr/lib)")
  133. if(d_is_system_lib)
  134. set(system_deps ${system_deps} "${d}")
  135. else(d_is_system_lib)
  136. if(d_is_embedded_lib)
  137. set(embedded_deps ${embedded_deps} "${d}")
  138. else(d_is_embedded_lib)
  139. set(nonsystem_deps ${nonsystem_deps} "${d}")
  140. endif(d_is_embedded_lib)
  141. endif(d_is_system_lib)
  142. endforeach(d)
  143. message("")
  144. message("system_deps:")
  145. foreach(d ${system_deps})
  146. message("${d}")
  147. endforeach(d ${system_deps})
  148. message("")
  149. message("embedded_deps:")
  150. foreach(d ${embedded_deps})
  151. message("${d}")
  152. endforeach(d ${embedded_deps})
  153. message("")
  154. message("nonsystem_deps:")
  155. foreach(d ${nonsystem_deps})
  156. message("${d}")
  157. endforeach(d ${nonsystem_deps})
  158. message("")
  159. macro(copy_library_into_bundle clib_bundle clib_libsrc clib_dstlibs clib_fixups)
  160. #
  161. # If the source library is a framework, copy just the shared lib bit of the framework
  162. # into the bundle under "${clib_bundle}/Contents/Frameworks" - if it is just a dylib
  163. # copy it into the same directory with the main bundle executable under
  164. # "${clib_bundle}/Contents/MacOS"
  165. #
  166. if("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  167. # make sure clib_libsrc is a full path to the framework as a framework
  168. # maybe linked in with relative paths in some cases
  169. find_framework_full_path("${clib_libsrc}" fw_full_src)
  170. get_filename_component(fw_src "${fw_full_src}" ABSOLUTE)
  171. get_filename_component(fw_srcdir "${clib_libsrc}/../../.." ABSOLUTE)
  172. get_filename_component(fwdirname "${fw_srcdir}" NAME)
  173. string(REGEX REPLACE "^(.*)\\.framework$" "\\1" fwname "${fwdirname}")
  174. string(REGEX REPLACE "^.*/${fwname}\\.framework/(.*)$" "\\1" fwlibname "${clib_libsrc}")
  175. set(fw_dstdir "${clib_bundle}/Contents/Frameworks")
  176. # message("")
  177. # message("fwdirname: '${fwdirname}'")
  178. # message("fwname: '${fwname}'")
  179. # message("fwlibname: '${fwlibname}'")
  180. # message("fw_src: '${fw_src}'")
  181. # message("fw_srcdir: '${fw_srcdir}'")
  182. # message("fw_dstdir: '${fw_dstdir}'")
  183. # message("new_name: '@executable_path/../Frameworks/${fwdirname}/${fwlibname}'")
  184. # message("")
  185. message("Copying ${fw_srcdir} into bundle...")
  186. # This command copies the *entire* framework recursively:
  187. #
  188. # execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory
  189. # "${fw_srcdir}" "${fw_dstdir}"
  190. # )
  191. # This command copies just the main shared lib of the framework:
  192. # (This technique will not work for frameworks that have necessary
  193. # resource or auxiliary files...)
  194. #
  195. message("fw_src = [${fw_src}] fw_full_src = [${fw_full_src}]")
  196. message("Copy: ${CMAKE_COMMAND} -E copy \"${fw_src}\" \"${fw_dstdir}/${fwlibname}\"")
  197. execute_process(COMMAND ${CMAKE_COMMAND} -E copy
  198. "${fw_src}" "${fw_dstdir}/${fwlibname}"
  199. )
  200. get_filename_component(fw_src_path "${fw_src}" PATH)
  201. message("Checking ${fw_src_path}/Resources")
  202. if(EXISTS "${fw_src_path}/Resources")
  203. message("Copy: ${CMAKE_COMMAND} -E copy_directory \"${fw_src_path}/Resources/\" \"${fw_dstdir}/Resources/\"")
  204. execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory
  205. "${fw_src_path}/Resources/" "${fw_dstdir}/${fwdirname}/Resources/")
  206. endif(EXISTS "${fw_src_path}/Resources")
  207. execute_process(COMMAND install_name_tool
  208. -id "@executable_path/../Frameworks/${fwlibname}"
  209. "${clib_bundle}/Contents/Frameworks/${fwlibname}"
  210. )
  211. set(${clib_dstlibs} ${${clib_dstlibs}}
  212. "${clib_bundle}/Contents/Frameworks/${fwlibname}"
  213. )
  214. set(${clib_fixups} ${${clib_fixups}}
  215. "-change"
  216. "${clib_libsrc}"
  217. "@executable_path/../Frameworks/${fwlibname}"
  218. )
  219. else("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  220. if("${clib_libsrc}" MATCHES "/")
  221. set(clib_libsrcfull "${clib_libsrc}")
  222. else("${clib_libsrc}" MATCHES "/")
  223. set(clib_libsrcfull "${lib_path}/${clib_libsrc}")
  224. if(NOT EXISTS "${clib_libsrcfull}")
  225. message(FATAL_ERROR "error: '${clib_libsrcfull}' does not exist...")
  226. endif(NOT EXISTS "${clib_libsrcfull}")
  227. endif("${clib_libsrc}" MATCHES "/")
  228. get_filename_component(dylib_src "${clib_libsrcfull}" ABSOLUTE)
  229. get_filename_component(dylib_name "${dylib_src}" NAME)
  230. set(dylib_dst "${clib_bundle}/Contents/MacOS/${dylib_name}")
  231. # message("dylib_src: ${dylib_src}")
  232. # message("dylib_dst: ${dylib_dst}")
  233. # message("new_name: '@executable_path/${dylib_name}'")
  234. message("Copying ${dylib_src} into bundle...")
  235. execute_process(COMMAND ${CMAKE_COMMAND} -E copy
  236. "${dylib_src}" "${dylib_dst}")
  237. execute_process(COMMAND install_name_tool
  238. -id "@executable_path/${dylib_name}"
  239. "${dylib_dst}"
  240. )
  241. set(${clib_dstlibs} ${${clib_dstlibs}}
  242. "${dylib_dst}"
  243. )
  244. set(${clib_fixups} ${${clib_fixups}}
  245. "-change"
  246. "${clib_libsrc}"
  247. "@executable_path/${dylib_name}"
  248. )
  249. endif("${clib_libsrc}" MATCHES ".framework/.*/.*/.*")
  250. endmacro(copy_library_into_bundle)
  251. # Copy dependent "nonsystem" libraries into the bundle:
  252. #
  253. message("Copying dependent libraries into bundle...")
  254. set(srclibs ${nonsystem_deps} ${extra_libs})
  255. set(dstlibs "")
  256. set(fixups "")
  257. foreach(d ${srclibs})
  258. message("copy it --- ${d}")
  259. copy_library_into_bundle("${bundle}" "${d}" dstlibs fixups)
  260. endforeach(d)
  261. message("")
  262. message("dstlibs='${dstlibs}'")
  263. message("")
  264. message("fixups='${fixups}'")
  265. message("")
  266. # Fixup references to copied libraries in the main bundle executable and in the
  267. # copied libraries themselves:
  268. #
  269. if(NOT "${fixups}" STREQUAL "")
  270. message("Fixing up references...")
  271. foreach(d ${dstlibs} "${input_file_full}")
  272. message("fixing up references in: '${d}'")
  273. execute_process(COMMAND install_name_tool ${fixups} "${d}")
  274. endforeach(d)
  275. message("")
  276. endif(NOT "${fixups}" STREQUAL "")
  277. # List all references to eyeball them and make sure they look right:
  278. #
  279. message("Listing references...")
  280. foreach(d ${dstlibs} "${input_file_full}")
  281. execute_process(COMMAND otool -L "${d}")
  282. message("")
  283. endforeach(d)
  284. message("")
  285. # Output file:
  286. #
  287. #get_filename_component(script_name "${CMAKE_CURRENT_LIST_FILE}" NAME)
  288. #file(WRITE "${input_file_full}_${script_name}" "# Script \"${CMAKE_CURRENT_LIST_FILE}\" completed.\n")
  289. message("")
  290. message("# Script \"${CMAKE_CURRENT_LIST_FILE}\" completed.")
  291. message("")