Installation behavior of shared versus static libraries

Context

I was going through Step 5 of the CMake tutorial, and after completing TODO items 1 through 4 to understand the installation process I noticed what I thought was some strange behavior in the generated Make buildsystem. Specifically, after running the following commands:

cmake ../Step5
cmake --build .

from my Step5_build folder, on the first run of cmake --install . --prefix /my/install/directory the resulting output was the following:

-- Install configuration: ""
-- Installing: /my/install/directory/lib/libMathFunctions.a
-- Installing: /my/install/directory/include/MathFunctions.h
-- Installing: /my/install/directory/bin/Tutorial
-- Installing: /my/install/directory/include/TutorialConfig.h

On the second run of cmake --install . --prefix /my/install/directory, the output was the following:

-- Install configuration: ""
-- Installing: /my/install/directory/lib/libMathFunctions.a
-- Up-to-date: /my/install/directory/include/MathFunctions.h
-- Up-to-date: /my/install/directory/bin/Tutorial
-- Up-to-date: /my/install/directory/include/TutorialConfig.h

However, when I decided to change the following line from Step5/MathFunctions/CMakeLists.txt:

add_library(MathFunctions mysqrt.cxx)

to this:

add_library(MathFunctions SHARED mysqrt.cxx)

after regenerating the buildsystem and re-building the targets, I got the following output on the next run of cmake --install . --prefix /my/install/directory:

-- Install configuration: ""
-- Installing: /my/install/directory/lib/libMathFunctions.dylib
-- Up-to-date: /my/install/directory/include/MathFunctions.h
-- Installing: /my/install/directory/bin/Tutorial
-- Up-to-date: /my/install/directory/include/TutorialConfig.h

and this output on the subsequent run of cmake --install . --prefix /my/install/directory:

-- Install configuration: ""
-- Up-to-date: /my/install/directory/lib/libMathFunctions.dylib
-- Up-to-date: /my/install/directory/include/MathFunctions.h
-- Installing: /my/install/directory/bin/Tutorial
-- Up-to-date: /my/install/directory/include/TutorialConfig.h

Question

I am wondering why making a shared library would cause it to install once and show as up-to-date on subsequent invocations of cmake --install whereas using the default library type (which is ostensibly STATIC on my system) would cause it to have to be installed regardless of whether cmake --install had already been invoked.

Additionally, given the details from the context above, I would like to know why turning my library into a shared library would cause a dependent target executable to repeatedly install on each invocation of cmake --install whereas using a static library would cause the dependent executable to install only once.

I am very new to CMake and building C/C++ projects in general, so I would greatly appreciate any clarifications that would help me clear my doubts.

My Environment

CMake: 3.26.4
OS: macOS Ventura 13.4

The default library type is controlled by BUILD_SHARED_LIBS (usually a cache variable). Anyways, I would check the cmake_install.cmake code for each; there may be some difference here?

On macOS, there is some binary patching to fixup rpath entries between the build and install tree. This may tinker with byte-exact files and cause a “new” install to happen.

Hi Ben, thanks so much for taking the time to look into this. Following your suggestion, I decided to create two separate copies of the tutorial to try out both the static and dynamic library versions, and, to your point earlier, there were indeed differences between the two versions of the Step5_build/cmake_install.cmake file as well as between the two versions of the Step5_build/MathFunctions/cmake_install.cmake file!

In the former file, after the binary Tutorial is installed, within an IF() condition that checks for the existence of the binary in the install tree and ensures that it is not a symlink, there is an execute_process that invokes the macOS install_name_tool binary with the -delete_rpath option to remove the build tree version of the Step5_build/MathFunctions directory path from rpath of the executable in the install tree.

In the Step5_build/MathFunctions/cmake_install.cmake file, in the static library version, after the install of the library is performed, within a check to ensure that the library is present in the install tree and is not a symlink, there is an execute_process that invokes the macOS ranlib binary on the installed library. However, in the corresponding code for the dynamic library, there is an additional check for the presence of the CMAKE_INSTALL_DO_STRIP variable, and only if said variable is true will the CMake code run execute_process to invoke the strip binary on the generated dynamic library. I modified the CMake code to have an additional ELSE() branch that uses execute_process to output “I’m not invoking strip” to a text file on my system, and lo and behold when I ran cmake --install again that file was present with the expected contents!

I think, as you surmised, these differences explain all the behavior I was confused about originally.

Thanks so much once again for the suggestions. As a side note, I had not known what an rpath was before, so I was happy to learn about that. I feel like I have a bit better appreciation of what’s going on behind the scenes now.

Addendum

I wanted to attach the diff files I made to explore what was going on, but unfortunately it seems that as a new user I cannot perform that action.