Migration experiences / Comparison RUNTIME_DEPENDENCY_SET vs. fixup_bundle (BundleUtilities)

TL;DR: For a summary of my findings and still open questions, see this post below!

I’m currently reading up on and experimenting with the RUNTIME_DEPENDENCY_SET functionality. In trying to set up a RUNTIME_DEPENDENCY_SET based solution for our project I get an error output from cpack:

CMake Error at ....cmake:111 (file):
  file Could not resolve runtime dependencies:

    HvsiFileTrust.dll
    OurBaseLibrary.dll
    AnotherOfOurLibraries.dll
    ...

(This obviously is on windows). I don’t know yet how to debug further, cpack --verbose just tells me what libraries it is installing but nothing about the dependency checking process. Also, is the error message above trying to tell me that it can’t find those libraries itself it mentions, or that it wasn’t able to find dependencies of those libraries? The first entry (I guess some Microsoft library, definitely not created by our build) makes me think the first, but the other entries (shared libraries created within the very same build that this cpack is run on, somewhat suggesting the second; I do not link directly to the .dll files created for OurBaseLibrary/AnotherOfOurLibraries directly anywhere, just via target name.

So my main question is: How can I debug the dependency finding process for RUNTIME_DEPENDENCY_SET? What can I do to see where those .dll dependencies are coming from?

On a bit of a tangent, previously in that project I was using BundleUtilities to create standalone packages. My experiments started because RUNTIME_DEPENDENCY_SET seems a little bit simpler, more “core CMake” and targetting the exact same goal of creating a standalone application bundle.

So I’m also wondering about the differences between the two, as well as respective advantages/disadvantages. BundleUtilities possibly targets more specifically at Apple (but is also working on other platforms?). From my perspective it would be helpful to add references in the documentations to the respective other one, ideally with short hints on the situations in which one or the other is more suitable. At the moment I do not feel knowledgable enough to be make a useful contribution there myself though.

Regarding debug output, I’ve tried cpack --trace, but there the last command is just the file(get_runtime_dependencies ...) command, which helps a little but still does not give any insight into the actual dependency tracing.

I’ve now narrowed it down a little by starting with

install(TARGET OurBaseLibary RUNTIME_DEPENCENCY_SET OurBaseLibDepSet ...)
install(RUNTIME_DEPENDENCY_SET OurBaseDepSet
	DIRECTORIES library-directories
	PRE_EXCLUDE_REGEXES
		[[HvsiFileTrust.*]]
		[[PdmUtilities.*]]
		[[api-ms-win-.*]]
		[[ext-ms-.*]]
	)

and it seems to be able to find all directly required libraries in the given library-directories, but not some system libraries:

CMake Error at ....cmake_install.cmake:67 (file):
  file Could not resolve runtime dependencies:

    HvsiFileTrust.dll
    PdmUtilities.dll
Call Stack (most recent call first):

I had an error also about api-ms-win-* and ext-ms-* dlls before when I forgot the PRE_EXCLUDE_REGEXES in the install(RUNTIME_DEPENDENCY_SET ...) call, so I’m rather sure that there is just something wrong with my regex for excluding HvsiFileTrust and PdmUtilities?

I guess I should read the documentation more carefully:

Library names are all expected in lower case.

What remains is the original question why RUNTIME_DEPENDENCY_SET does not find libraries that are targets within the same project in our case. I can work around this by adding these as excludes, but this is less than ideal…
In the trace output, I see that the file(GET_RUNTIME_DEPENDENCIES…) call contains an entry like this:

... POST_EXCLUDE_FILES_STRICT C:/FullPathTo/OurBaseLibrary.dll

where does this (undocumented) parameter come from, and could this be the cause why the OurBaseLibrary entry turns up being reported as not being found?

Regarding how to exclude the Windows system libraries, CMake Professional 18th edition recommends to use

	POST_EXCLUDE_REGEXES
		[[.*/system32/.*\.dll]]

which I had included in the install(RUNTIME_DEPENDENCY_SET call, but there are still a lot of .dlls from the system32 folder copied to the final package. When I use [[.*system32.*\.dll]] instead it seems to work. I also tried \ instead of / but that also didn’t work. What’s the correct way of specifying a directory separator here?

Basically, bundling based on RUNTIME_DEPENDENCY_SET now works on my project - with one exception. I would like to bundle a different version of onnxruntime.dll than what is contained in system32 - but RUNTIME_DEPENDENCY_SET always searches in sytem32 first (and of course finds something there) - but I don’t include any dlls from system32 (see above), so no version of onnxruntime is included at all. There currently seems no way to address this from within RUNTIME_DEPENENCY_SET installation, I guess I’ll have to install the onnxruntime manually? This does work as I would expect it with fixup_bundle - I believe fixup_bundle only searches in the specified directories? I don’t really get the use case for under Windows searching under system32 first - shouldn’t .dll’s from there never be included anyway? Is this just for “filtering out” libraries that are coming from the system?

To sum up my experiments:

  • RUNTIME_DEPENDENCY_SET appears a bit more flexible regarding specific libraries to be explicitly excluded; it searches transitively for dependencies, which can be an advantage but also a disadvantage since unwanted system libraries have to be excluded manually. It is less flexible regarding search paths - if a .dll exists in system32, no alternative can be used, the shared library has to be “manually” installed.
  • BundleUtilities fixup_bundle in contrast allows explicitly specifying the searched directories (and only those are searched, in contrast to above where e.g. under windows, C:\Windows and C:\Windows\system32 are always searched).

Regarding how to exclude the Windows system libraries, CMake Professional (18th ed) gives an example of adding

	POST_EXCLUDE_REGEXES
		[[.*/system32/.*\.dll]]

to the install(RUNTIME_DEPENDENCY_SET call, but using this I saw many .dlls from the system32 folder being copied to the final package. When I use [[.*system32.*\.dll]] instead it seems to work. I also tried \ instead of / but that also didn’t work. What’s the correct way of specifying a directory separator here?

Basically, bundling based on RUNTIME_DEPENDENCY_SET now works on my project - with one minor exception. I would like to bundle a different version of onnxruntime.dll than what is contained in system32 - but RUNTIME_DEPENDENCY_SET always searches in sytem32 first (and of course finds something there) - but I don’t want to include any dlls from system32 (see above), so no version of onnxruntime is included at all. There currently seems no way to address this from within RUNTIME_DEPENENCY_SET installation, I guess I’ll have to install the onnxruntime manually? This does work as I would expect it with fixup_bundle - I believe fixup_bundle only searches in the specified directories? I don’t really get the use case for under Windows searching under system32 first - shouldn’t .dll’s from there never be included anyway? Is this just for “filtering out” libraries that are coming from the system?

Open issues:

  • Find correct way of specifying directory separator in POST_EXCLUDE_REGEXES (or simply ignore and skip every .dll that has system32 somewhere in the full path, I don’t expect any future libraries to have this string in their names or so).
  • Find a way to skip Windows / system32 directory for a shared library / work around it by installing dll file directly via install(FILES...)

To sum up my findings regarding differences:

  1. RUNTIME_DEPENDENCY_SET:
    • appears a bit more flexible regarding specific libraries to be explicitly excluded
    • searches transitively for dependencies, which can be an advantage but also a disadvantage since unwanted system libraries have to be excluded manually
    • less flexible regarding search paths - if a .dll exists in system32, no alternative can be used, the shared library has to be “manually” installed.
    • much faster than (2) (~ 30sec total cpack time on my machine)
  2. BundleUtilities fixup_bundle in contrast:
    • allows explicitly specifying the searched directories (and only those are searched, in contrast to (1), where e.g. under windows, C:\Windows and C:\Windows\system32 are always searched)
    • requires a pre-set-up environment to be able to resolve dependencies (such as a the “… Command Prompt for VS 20xx” provide), otherwise it will not do any dependency resolution (without reporting any error)
    • significantly slower than (1) (~ 4min 20sec total cpack time on my machine)

I just retested this again now and I think I see the problem. While [[.*/system32/.*\.dll]] works for most dependencies, it appears that internally some paths end up with C:\Windows\system32/somedep.dll as the path to compare against. Note the backslash before system32 instead of a forward slash. The CMake documentation doesn’t make any statement about the path separators used when doing the regex matching, so unfortunately projects will have to account for both if they want their regex to work in all cases. I’ll add a note about this in the next edition of Professional CMake. A modified working example of the above would be:

	POST_EXCLUDE_REGEXES
		[[.*(\\|/)system32(\\|/).*\.dll]]
1 Like

I opened a new issue for this in CMake’s issue tracker here: https://gitlab.kitware.com/cmake/cmake/-/issues/26202

1 Like

Thanks a lot, your suggested updated regular expression is also working for me!