'cmake --install'/'make install'/'ninja install' doesn't install dependencies

I’m integrating a Makefile’s functionality into CMake. The Makefile builds and installs a target. The install copies from a target’s install directory into the toolchain sysroot thus making it available for dependent targets. When building all targets, the install is performed for the dependency chain automatically.

I’m trying have this same functionality with CMake. Except ‘cmake --install’/‘make install’/‘ninja install’ performs a build of dependencies but doesn’t install them before the dependent target build starts. This causes a build failure because dependent files are not found.

I’m using ExternalProject to build open source packages. It performs all of the necessary steps to for ‘cmake --install’/‘make install’/‘ninja install’ to function.

Here is the install function for each open source package:

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/install/
    DESTINATION ${CMAKE_SYSROOT_REL}
    USE_SOURCE_PERMISSIONS
    COMPONENT ${PROJECT_NAME}
)

For example, openssl depends upon zlib. The openssl/CMakeLists.txt has this line:

add_dependencies(${PROJECT_NAME} zlib)

This causes zlib to be built before openssl by ‘ninja install’. But zlib isn’t installed.

This forces me to perform builds and installs manually which isn’t following the functionality of the original Makefile:
cmake --build . --target zlib
cmake --install . --component zlib
cmake --build . --target openssl
cmake --install . --component openssl

Is there something I’m missing or is this a limitation with CMake?

You should work with targets, not with files or directories :open_mouth:

I don’t understand this response. I’m am working with targets not files or directories.
In this case, the problem is the ‘–install’ option doesn’t perform an install on dependencies.

you install a directory, not a target!

Ok I see what you were referring to.

As I mentioned, I’m using ExternalProject to build open source packages. When I try to call install with the following:

install(TARGETS ${PROJECT_NAME}
    DESTINATION ${CMAKE_SYSROOT_REL}
    COMPONENT ${PROJECT_NAME}
)

I get this error:

  install TARGETS given target "zlib" which is not an executable, library, or
  module.

I can’t help you if you show only snippets without context!

A minimum project to show you problems like this would really help:

cmake_minimum_required(VERSION 3.25...3.31)
project(OpenSSL_ExternalProject LANGUAGES C)

include(ExternalProject)

# Set OpenSSL version and source location
set(OPENSSL_VERSION "3.4.0") # Ändere die Version bei Bedarf
set(OPENSSL_URL "https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz")

# Define installation directory
set(OPENSSL_INSTALL_DIR ${CMAKE_BINARY_DIR}/openssl-install)

# Add ExternalProject to download, configure, and build OpenSSL
if(OPENSSL_INSTALL_DIR)
  ExternalProject_Add(
    openssl
    URL ${OPENSSL_URL}
    URL_HASH SHA256=e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf
    PREFIX ${CMAKE_BINARY_DIR}/openssl-src
    SOURCE_SUBDIR .
    CONFIGURE_COMMAND ${CMAKE_BINARY_DIR}/openssl-src/src/openssl/config --prefix=${OPENSSL_INSTALL_DIR}
                      --openssldir=${OPENSSL_INSTALL_DIR}/ssl
    BUILD_COMMAND make -C <BINARY_DIR> -j8
    INSTALL_COMMAND make -C <BINARY_DIR> -j8 install
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON
    LOG_INSTALL ON
  )

  # Optionally, add OpenSSL include and library directories for other targets
  set(OPENSSL_INCLUDE_DIR ${OPENSSL_INSTALL_DIR}/include)
  set(OPENSSL_LIB_DIR ${OPENSSL_INSTALL_DIR}/lib)

  # Display helpful messages
  message(STATUS "OpenSSL will be installed to: ${OPENSSL_INSTALL_DIR}")
  message(STATUS "Include directory: ${OPENSSL_INCLUDE_DIR}")
  message(STATUS "Library directory: ${OPENSSL_LIB_DIR}")

  add_executable(my_app main.c)
  target_include_directories(my_app PRIVATE ${OPENSSL_INCLUDE_DIR})
  target_link_directories(my_app PRIVATE ${OPENSSL_LIB_DIR})
  target_link_libraries(my_app PRIVATE ssl crypto)
  add_dependencies(my_app openssl_build)

else()
  ExternalProject_Add(
    openssl
    URL ${OPENSSL_URL}
    URL_HASH SHA256=e15dda82fe2fe8139dc2ac21a36d4ca01d5313c75f99f46c4e8a27709b7294bf
    PREFIX ${CMAKE_BINARY_DIR}/openssl-src
    SOURCE_SUBDIR .
    CONFIGURE_COMMAND ${CMAKE_BINARY_DIR}/openssl-src/src/openssl/config --prefix=/opt/local --openssldir=/opt/local/ssl
    BUILD_COMMAND make -C <BINARY_DIR> -j8
    INSTALL_COMMAND make -C <BINARY_DIR> -j8 install
    LOG_DOWNLOAD ON
    LOG_CONFIGURE ON
    LOG_BUILD ON
    LOG_INSTALL ON
  )
endif()

# Create a target that depends on the OpenSSL build
add_custom_target(openssl_build ALL DEPENDS openssl)

Building ExternalProjects in a specific order and manage dependencies between them is called a superbuild. Only ExternalProjects builds are done there.

You can only integrate ExternalProjects in a normal build if you do not rely on installed items at configure time. I guess that building openssl also configures it. You sometimes can work around this a bit by specifying the install target as build target in ExternalProject with a custom BUILD_COMMAND (or both commands if the install does not depend on build).

The other option is to tightly integrate the build using FetchContent which can bring its own set of problems.

It is in any case easier to have a tool that properly builds a sysroot that you can rely on instead of using cmake as a hammer to handle the screw.

I do the building of dependencies and it works quite nice. If you can post more of your code (e.g. the external_project), we might find the issue.

Below is the CMake code for openssl and zlib.

In the openssl code there an add_dependencies line for zlib. This assures that zlib is built before openssl. But, as I’ve pointed out, zlib doesn’t get installed when using ‘ninja install’ or ‘cmake --install .’

project(openssl)

set(TARBALL_VERSION 3.0.13)
set(MD5 c15e53a62711002901d3515ac8b30b86)

set(EXPORTS
    "export CFLAGS='-march=armv8-a -Wall -g -O3'"
    "export CXXFLAGS='-march=armv8-a -Wall -g -O3'"
    "export LDFLAGS='-Wl,--build-id'"
    "export PKG_CONFIG_SYSROOT_DIR='${CMAKE_SYSROOT}'"
    "export PKG_CONFIG_PATH='${CMAKE_SYSROOT}/usr/lib/pkgconfig'"
)
string(REPLACE ";" "\n" EXPORTS "${EXPORTS}")
configure_file(${CMAKE_PATH}/build-wrapper.sh.in build-wrapper.sh)

include(ExternalProject)
ExternalProject_Add(${PROJECT_NAME}
    DOWNLOAD_EXTRACT_TIMESTAMP TRUE
    PREFIX ${CMAKE_CURRENT_BINARY_DIR}
    INSTALL_DIR install
    #--Download step--------------
    URL https://github.com/openssl/openssl/releases/download/openssl-${TARBALL_VERSION}/${PROJECT_NAME}-${TARBALL_VERSION}.tar.gz
    URL_MD5 ${MD5}
    #--Update/Patch step----------
    #--Configure step-------------
    USES_TERMINAL_CONFIGURE TRUE
    CONFIGURE_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        ./Configure
            --prefix=/usr
            # --libdir=lib
            --openssldir=/etc/ssl
            no-capieng
            no-cms
            no-gost
            no-makedepend
            no-srtp
            no-tests
            no-aria
            no-bf
            no-blake2
            no-camellia
            no-cast
            no-cmac
            no-cmp
            no-idea
            no-mdc2
            no-ocb
            no-rc2
            no-rc4
            no-rmd160
            no-scrypt
            no-seed
            no-siphash
            no-sm2
            no-sm3
            no-sm4
            no-ssl-trace
            no-whirlpool
            zlib
            linux-aarch64
    #--Build step-----------------
    USES_TERMINAL_BUILD TRUE
    BUILD_IN_SOURCE 1
    BUILD_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        make
    #--Install step---------------
    USES_TERMINAL_INSTALL TRUE
    INSTALL_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        make
            install
            DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/install
)
add_dependencies(${PROJECT_NAME} zlib)

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/install/
    DESTINATION ${CMAKE_SYSROOT_REL}
    USE_SOURCE_PERMISSIONS
    COMPONENT ${PROJECT_NAME}
)

string(TOUPPER ${PROJECT_NAME} TARGET)
set(CPACK_ARCHIVE_${TARGET}_FILE_NAME ${PROJECT_NAME}-${TARBALL_VERSION}-${CMAKE_SYSTEM_PROCESSOR} CACHE INTERNAL "")

project(zlib)

set(TARBALL_VERSION 1.2.12)
set(MD5 5fc414a9726be31427b440b434d05f78)

if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL x86_64)
    set(CFLAGS -m64 -Wall -g -O3 -fPIC)
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL arm)
    set(CFLAGS -march=armv8-a -g -O2 -fPIC)
endif()
string(REPLACE ";" " " CFLAGS "${CFLAGS}")

set(EXPORTS
    "export CFLAGS='${CFLAGS}'"
)
string(REPLACE ";" "\n" EXPORTS "${EXPORTS}")
configure_file(${CMAKE_PATH}/build-wrapper.sh.in build-wrapper.sh)

include(ExternalProject)
ExternalProject_Add(${PROJECT_NAME}
    DOWNLOAD_EXTRACT_TIMESTAMP TRUE
    PREFIX ${CMAKE_CURRENT_BINARY_DIR}
    INSTALL_DIR install
    #--Download step--------------
    URL https://www.zlib.net/${PROJECT_NAME}-${TARBALL_VERSION}.tar.gz
    URL_MD5 ${MD5}
    #--Update/Patch step----------
    #--Configure step-------------
    USES_TERMINAL_CONFIGURE TRUE
    CONFIGURE_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        ./configure
            --prefix=/usr
            # --libdir=/lib
            --uname=Linux
            --shared
    #--Build step-----------------
    USES_TERMINAL_BUILD TRUE
    BUILD_IN_SOURCE 1
    BUILD_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        make
    #--Install step---------------
    USES_TERMINAL_INSTALL TRUE
    INSTALL_COMMAND
        ${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
        make
            install
            DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/install
)

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/install/
    DESTINATION ${CMAKE_SYSROOT_REL}
    USE_SOURCE_PERMISSIONS
    COMPONENT ${PROJECT_NAME}
)

string(TOUPPER ${PROJECT_NAME} TARGET)
set(CPACK_ARCHIVE_${TARGET}_FILE_NAME ${PROJECT_NAME}-${TARBALL_VERSION}-${CMAKE_SYSTEM_PROCESSOR} CACHE INTERNAL "")

is missing?

Use:

BUILD_COMMAND
${CMAKE_CURRENT_BINARY_DIR}/build-wrapper.sh
make
install
DESTDIR=${CMAKE_CURRENT_BINARY_DIR}/install

to install it right after build.

Why perform the install during the BUILD_COMMAND? The INSTALL_COMMAND works just fine in that the package files are installed to the install directory after BUILD_COMMAND completes.

The issue is not with the ExternalProject_Add INSTALL_COMMAND. The issue is that CMake, or rather CPack, doesn’t perform an install of dependencies during ‘ninja install’. It performs a build of dependencies but doesn’t install them.

I have no additional projects for my dependencies but have an

ExternalProject_Add(zlib
....)

to make

work. When the external project zlib is built, it is also installed.

But maybe I just don’t get your point.

I think this captures the heart of your problem. The INSTALL_COMMAND part of ExternalProject_Add() defines how to install the external project into a place that your main project can read it. It has nothing to do with installing the main project. If you want anything from the external project to be installed along with your main project, the main project is responsible for defining how that should happen.

That’s what the code below is for. This is tells ‘ninja install’ what to do. Without it, ‘ninja install’ would do nothing.

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/install/
    DESTINATION ${CMAKE_SYSROOT_REL}
    USE_SOURCE_PERMISSIONS
    COMPONENT ${PROJECT_NAME}
)

Is that in your main project, or in the dependency subproject?

All projects have the same install.

Ok, but that doesn’t answer my question. Putting that in the dependency sub builds won’t cause it to be installed as part of installing the main project (at least, not on its own). If you put it in the main project, that might achieve what you want, as long as each sub build installed things to wherever ${CMAKE_CURRENT_BINARY_DIR}/install/ points to in the main project.

@craig.scott What do you mean by “main project”?

When you do a ninja install this is for the main project. I agree with Craig with his statement

I think this captures the heart of your problem. The INSTALL_COMMAND part of ExternalProject_Add() defines how to install the external project into a place that your main project can read it. It has nothing to do with installing the main project . If you want anything from the external project to be installed along with your main project, the main project is responsible for defining how that should happen.

When you build your dependencies via external project, these are built and installed in one rush. You need to make sure this implicit install is done to the right place. This is either locally in your build such that your main project finds the artefacts or probably already to the system. In the first case (what I do myself) you might want to add the artefacts from the dependencies by the install() of the main projection.

What is the output when your external projects are build? I have

[  0%] Creating directories for 'zlib'
[  0%] Performing download step (verify and extract) for 'zlib'
...
[ 50%] No update step for 'zlib'
[ 50%] No patch step for 'zlib'
[100%] Performing configure step for 'zlib'
...
[100%] Performing build step for 'zlib'
...
[100%] Performing install step for 'zlib'
...
[100%] Completed 'zlib'

Zlib is a cmake package and when built to the directory zlib-build, a file install_manifest.txt is created, containing the files to be installed within the external_project.

And again, to have the dependency zlib to be resolved, you need to call your external project as zlib - at least I did not see it in your quoted code that you do so.