From 7962186b3f0f9089370dacf832995c3e417b259d Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Wed, 19 Jun 2019 20:45:53 +0200 Subject: [PATCH] bundle: store dll in lib subdirectory, bundle all of them at once instead of finding and copying the dll each time a binary is produced, the path of the produced binary is stored in a unique file (to avoid race condition) and the target for that binary is added as a dependency to a new target named `bundle` that does the finding and the copying once for all. this avoids race condition while building targets in parallel, workaround the inability of cmake to track a common list between all targets and to keep it from configure to build step this makes the bundle process more reliable and avoid things being copied twice. Before that I've seen copy processes starting to copy the same file even with the -n option telling to not copy if the file exists, then seeing one of them failing because they both started when the file was not yet copied but one finished the copy before the other one this also makes the bundling faster by only doing things once for all the dll are then moved to a lib/ subdirectory instead of the root of the buld directory each binary having dll to find and copy is built with a special resource file embedding a manifest telling there is an assembly directory containing dll named lib, then a manifest is writen for that assembly directory listing all the dll it can find there telling windows to look for them there at binary loading instead of the system directory and the place right to binary this: - helps to ensure the right dll is loaded even if another exists with the same name on the system - reduce the mess by storing all dll in one dedicated directory - avoids the double cmake configure run by removing the need to glob the build directory before installation --- CMakeLists.txt | 32 ++++------ README.md | 6 -- bundle/CMakeLists.txt | 18 ++++++ include/lib.rc | 11 ++++ library-bundler | 138 ++++++++++++++++++++++++++++------------- radiant/CMakeLists.txt | 1 - 6 files changed, 137 insertions(+), 69 deletions(-) create mode 100644 bundle/CMakeLists.txt create mode 100644 include/lib.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index 154a873d..1a414aa1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,30 +213,18 @@ if (BUILD_BINARIES) option(BUNDLE_LIBRARIES "Bundle libraries" ${BUNDLE_LIBRARIES_DEFAULT}) + if (BUNDLE_LIBRARIES) + add_subdirectory(bundle) + endif () + macro (copy_dlls target) - if (BUNDLE_LIBRARIES AND WIN32) + if (BUNDLE_LIBRARIES) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${PROJECT_SOURCE_DIR}/library-bundler" - ARGS "windows" "$" "${PROJECT_BINARY_DIR}" + "register" "${PROJECT_BINARY_DIR}" "$" VERBATIM ) - endif () - endmacro () - - macro (bundle_stuff target) - if (BUNDLE_LIBRARIES AND WIN32) - file(GLOB DLL_FILES ${PROJECT_BINARY_DIR}/*.dll) - - install(FILES - ${DLL_FILES} - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) - - install(DIRECTORY - ${PROJECT_BINARY_DIR}/lib - ${PROJECT_BINARY_DIR}/share - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) + add_dependencies(bundle ${target}) endif () endmacro () endif () @@ -279,7 +267,11 @@ if (BUILD_BINARIES) endif () macro (radiant_tool name) - add_executable(${name} ${ARGN}) + if (BUNDLE_LIBRARIES AND WIN32) + add_executable(${name} ${ARGN} ${PROJECT_SOURCE_DIR}/include/lib.rc) + else () + add_executable(${name} ${ARGN}) + endif () copy_dlls(${name}) diff --git a/README.md b/README.md index 83a7f6c7..8aa17a42 100644 --- a/README.md +++ b/README.md @@ -161,12 +161,6 @@ target: * `install` Install files -Note that because of both the way NetRadiant works and the way bundled library loading works CMake has to do some globbing to detect some of the produced/copied files it has to install. So you have to run cmake again before installing: - -``` -cmake -H. -Bbuild && cmake --build build -- install -``` - ## Note about Crunch The crnlib used to decode `.crn` files is the one from [Dæmon](http://github.com/DaemonEngine/Daemon) which is the one by [Unity](https://github.com/Unity-Technologies/crunch/tree/unity) made cross-platform and slightly improved. Since Unity brokes compatibility with [BinomialLLC's legacy tree](https://github.com/BinomialLLC/crunch) it's required to use either the `crunch` tool from Dæmon or the one from Unity to compress textures that have to be read by radiant or q3map2. diff --git a/bundle/CMakeLists.txt b/bundle/CMakeLists.txt new file mode 100644 index 00000000..1cb5a221 --- /dev/null +++ b/bundle/CMakeLists.txt @@ -0,0 +1,18 @@ +if (WIN32) + set(BUNDLE_OS_NAME "windows") +else () + set(BUNDLE_OS_NAME "unsupported") +endif () + +add_custom_target(bundle ALL + COMMAND "${PROJECT_SOURCE_DIR}/library-bundler" + "${BUNDLE_OS_NAME}" "${PROJECT_BINARY_DIR}" + VERBATIM + COMMENT "Bundling libraries" +) + +install(DIRECTORY + ${PROJECT_BINARY_DIR}/lib + ${PROJECT_BINARY_DIR}/share + DESTINATION ${CMAKE_INSTALL_PREFIX} +) diff --git a/include/lib.rc b/include/lib.rc new file mode 100644 index 00000000..3ad3a122 --- /dev/null +++ b/include/lib.rc @@ -0,0 +1,11 @@ +1 24 /* RT_MANIFEST */ +BEGIN + "" + "" + "" + "" + "" + "" + "" + "" +END diff --git a/library-bundler b/library-bundler index 7741e63e..c2855be6 100644 --- a/library-bundler +++ b/library-bundler @@ -1,51 +1,105 @@ #! /usr/bin/env bash -system_name="${1}" -exe_file="${2}" -bundle_dir="${3}" +Windows::listLibForManifest () { + local lib_dir="${1}" + + find "${lib_dir}" \ + -maxdepth 1 \ + -type f \ + -name '*.dll' \ + -exec basename {} \; \ + | xargs -I {} \ + printf ' \n' +} + +Windows::printManifest () { + local lib_dir="${1}" + + cat <<-EOF + + + $(Windows::listLibForManifest "${lib_dir}") + + EOF +} + +Windows::bundleLibFromFile () { + local exe_file="${1}" + + exe_file="$(cygpath --unix "${exe_file}")" + + ntldd --recursive "${exe_file}" \ + | egrep -i '\.dll => [A-Z]:\\msys64\\' \ + | sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //' \ + | cygpath --unix --file - \ + | while read dll_file + do + dll_basename="$(basename "${dll_file}")" + + if [ -f "${bundle_dir}/${dll_basename}" ] + then + continue + fi + + cp --preserve=timestamps "${dll_file}" "${lib_dir}/${dll_basename}" + + if [ "${dll_basename}" = 'libgtk-win32-2.0-0.dll' ] + then + mingw="$(which 'libgtk-win32-2.0-0.dll' | cut -f2 -d'/')" + + for component_dir in \ + 'share/themes/MS-Windows' \ + 'share/icons/hicolor' \ + 'lib/gdk-pixbuf-2.0' \ + 'lib/gtk-2.0' + do + if ! [ -d "${bundle_dir}/${component_dir}" ] + then + mkdir --parents "${bundle_dir}/$(dirname "${component_dir}")" + cp -r --preserve=timestamps "/${mingw}/${component_dir}" \ + "${bundle_dir}/${component_dir}" + fi + done + + find "${bundle_dir}/lib" -type f -name '*.a' -exec rm {} \; + find "${bundle_dir}/lib" -type f -name '*.h' -exec rm {} \; + find "${bundle_dir}/lib" -type d -exec rmdir --ignore-fail-on-non-empty {} \; + fi + done +} + +system_name="${1}"; shift +bundle_dir="${1}"; shift +exe_file="${1}"; shift + +registry_dir="${bundle_dir}/registry" case "${system_name}" in + 'register') + mkdir --parents "${registry_dir}" + printf '%s\n' "${exe_file}" > "${registry_dir}/$(uuidgen)" + ;; 'windows') bundle_dir="$(cygpath --unix "${bundle_dir}")" - exe_file="$(cygpath --unix "${exe_file}")" - ntldd --recursive "${exe_file}" \ - | egrep -i '\.dll => [A-Z]:\\msys64\\' \ - | sed -e 's/ (0x[0-9a-f]*)$//;s/^.* => //' \ - | cygpath --unix --file - \ - | while read dll_file - do - dll_basename="$(basename "${dll_file}")" - - if [ -f "${bundle_dir}/${dll_basename}" ] - then - continue - fi - - cp --preserve=timestamps "${dll_file}" "${bundle_dir}/${dll_basename}" - - if [ "${dll_basename}" = 'libgtk-win32-2.0-0.dll' ] - then - mingw="$(which 'libgtk-win32-2.0-0.dll' | cut -f2 -d'/')" - - for component_dir in \ - 'share/themes/MS-Windows' \ - 'share/icons/hicolor' \ - 'lib/gdk-pixbuf-2.0' \ - 'lib/gtk-2.0' - do - if ! [ -d "${bundle_dir}/${component_dir}" ] - then - mkdir --parents "${bundle_dir}/$(dirname "${component_dir}")" - cp -r --preserve=timestamps "/${mingw}/${component_dir}" \ - "${bundle_dir}/${component_dir}" - fi - done - - find "${bundle_dir}/lib" -type f -name '*.a' -exec rm {} \; - find "${bundle_dir}/lib" -type f -name '*.h' -exec rm {} \; - find "${bundle_dir}/lib" -type d -exec rmdir --ignore-fail-on-non-empty {} \; - fi - done + + lib_dir="${bundle_dir}/lib" + mkdir --parents "${lib_dir}" + + if [ -d "${registry_dir}" ] + then + for registry_entry in "${registry_dir}"/* + do + exe_file="$(cat "${registry_entry}")" + + Windows::bundleLibFromFile "${exe_file}" + + rm "${registry_entry}" + rmdir --ignore-fail-on-non-empty "${registry_dir}" + done + + manifest_file="${lib_dir}/lib.manifest" + Windows::printManifest "${lib_dir}" > "${manifest_file}" + fi ;; *) printf 'ERROR: unsupported system: %s\n' "${system_name}" >&2 diff --git a/radiant/CMakeLists.txt b/radiant/CMakeLists.txt index 424c42d6..6fcb3680 100644 --- a/radiant/CMakeLists.txt +++ b/radiant/CMakeLists.txt @@ -131,4 +131,3 @@ if (X11_LIBRARIES) endif () copy_dlls(${RADIANT_BASENAME}) -bundle_stuff(${RADIANT_BASENAME}) -- 2.39.2