# How best to handle installation of public headers in ${CMAKE\_INSTALL\_INCLUDEDIR} and a subdirectory thereof

**URL:** https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525
**Category:** Code
**Created:** [February 24, 2026, 4:30am UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525 "2026-02-24T04:30:46Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![MarkCallow](https://discourse.cmake.org/user_avatar/discourse.cmake.org/markcallow/32/4823_2.png) [@MarkCallow](https://discourse.cmake.org/u/MarkCallow)
#### Post date: [February 24, 2026, 4:30am UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/1 "2026-02-24T04:30:46Z")

</div>

I have a library with two public header files that need to be installed: one in `${CMAKE_INSTALL_INCLUDEDIR}` and one in `${CMAKE_INSTALL_INCLUDEDIR}/SUBDIR`. If I do

```cmake
    set_target_properties(mylib PROPERTIES
        PUBLIC_HEADER a.h SUBDIR/b.h
    )

```

and an `install(TARGETS mylib …)` with

```cmake
        PUBLIC_HEADER
            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}

```

then both a.h and b.h are installed in `${CMAKE_INSTALL_INCLUDEDIR}.`

I tried to make an empty custom target to which I could add a PUBLIC\_HEADER property and for which I could add an `install(TARGETS …)` with

```cmake
        PUBLIC_HEADER
            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/SUBDIR

```

but I got an error from CMake that the target must be an executable, library or module target.

The solution I came up with is

```cmake
    get_target_property(framework mylib FRAMEWORK)
    if (NOT ${framework})
        install(DIRECTORY
            ${CMAKE_CURRENT_SOURCE_DIR}/SUBDIR/
            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/SUBDIR
        )
    else()
        # For frameworks, headers are installed in a different place For
        # this to work this file must be in the library source list.
        set_source_files_properties(
            SUBDIR/b.h
            PROPERTIES MACOSX_PACKAGE_LOCATION Headers/SUBDIR
        )
    endif()

```

but it is ugly and not linked to installing `mylib`.

Is there a better way?

---

<div class="post-metadata">

### Author: ![retif](https://discourse.cmake.org/user_avatar/discourse.cmake.org/retif/32/1776_2.png) [@retif](https://discourse.cmake.org/u/retif)
#### Post date: [February 24, 2026, 8:39am UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/2 "2026-02-24T08:39:20Z")

</div>

I remotely remember myself trying to install a “non-flat” directory of public headers via `PUBLIC_HEADER` target property, but as I recall that didn’t work, so I had to either go with `install(DIRECTORY ...)` (_like you did_), or iterate a list of headers and do `install(FILES ..)` for each (_using its `RELATIVE_PATH`_) (_which is even worse, unless one needs to install only some headers, hence the list_).

Indeed, that would not(?) be “linked” to installing the library’s target, but you could probably get around that by assigning the same `COMPONENT` to those `install()` commands and then install that component instead of the library target.

But actually you might want to try using [FILE\_SET](https://cmake.org/cmake/help/latest/command/target_sources.html#file-sets) instead (_if your CMake version is 3.23 or newer_), because it does support what you are describing. So if you have the following structure in your project:

```auto
$ tree ./include/
├── a.h
└── SUBDIR
    └── b.h

```

then the following should work:

```cmake
# ...

target_sources(mylib
    # actual sources
    # PRIVATE    
        # ...
    # public headers
    PUBLIC
        FILE_SET public_headers
            TYPE HEADERS
            BASE_DIRS include
            FILES
                include/a.h
                include/SUBDIR/b.h
)

install(TARGETS mylib
    EXPORT mylibTargets
    FILE_SET public_headers
)
# the rest of the installation (configs, etc)
# ...

```

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [February 24, 2026, 9:15pm UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/3 "2026-02-24T21:15:46Z")

</div>

> [@retif](#):
>
> But actually you might want to try using [FILE\_SET](https://cmake.org/cmake/help/latest/command/target_sources.html#file-sets) instead

I agree, but note that file sets can’t be defined on targets that are frameworks.

---

<div class="post-metadata">

### Author: ![MarkCallow](https://discourse.cmake.org/user_avatar/discourse.cmake.org/markcallow/32/4823_2.png) [@MarkCallow](https://discourse.cmake.org/u/MarkCallow)
#### Post date: [February 25, 2026, 11:11am UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/4 "2026-02-25T11:11:48Z")

</div>

Thank you @retif and @craig.scott. I have three questions.

1. Do I have to put all my sources in `target_sources`. They are currently listed in the `add_library` command. Or can I have just the `PUBLIC FILE SET` part there?
2. Can I add the FILE\_SET to the existing `install(TARGETS …)` that installs the library?
3. My target is only a framework when building for iOS. How do I deal with the framework in this case? Putting an `if(NOT framework)` around things can be used to hide them but how do I get the files installed. Do I have to use the source file properties trick?

---

<div class="post-metadata">

### Author: ![craig.scott](https://discourse.cmake.org/user_avatar/discourse.cmake.org/craig.scott/32/20_2.png) [@craig.scott](https://discourse.cmake.org/u/craig.scott)
#### Post date: [February 25, 2026, 9:58pm UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/5 "2026-02-25T21:58:00Z")

</div>

> [@MarkCallow](#):
>
> Do I have to put all my sources in `target_sources`. They are currently listed in the `add_library` command. Or can I have just the `PUBLIC FILE SET` part there?

You only need to list your headers in the `target_sources()` call when you’re adding them there to a FILE\_SET. You cannot define file sets in the `add_library()` call.

> [@MarkCallow](#):
>
> Can I add the FILE\_SET to the existing `install(TARGETS …)` that installs the library?

Yes. The `install(TARGETS...)` form accepts `FILE_SET` as one of the artifact-kind options (see [the docs](https://cmake.org/cmake/help/latest/command/install.html#targets)).

> [@MarkCallow](#):
>
> My target is only a framework when building for iOS. How do I deal with the framework in this case? Putting an `if(NOT framework)` around things can be used to hide them but how do I get the files installed. Do I have to use the source file properties trick?

Yes, most likely. `MACOSX_PACKAGE_LOCATION` should work for the framework case.

You mentioned that your target is only a framework when building for iOS. I assume you are producing some kind of SDK that will be distributed and others will build against? If not, then why add the headers to the framework? They would only be needed if building against the framework, not to simply use it at runtime.

---

<div class="post-metadata">

### Author: ![MarkCallow](https://discourse.cmake.org/user_avatar/discourse.cmake.org/markcallow/32/4823_2.png) [@MarkCallow](https://discourse.cmake.org/u/MarkCallow)
#### Post date: [March 5, 2026, 9:14am UTC](https://discourse.cmake.org/t/how-best-to-handle-installation-of-public-headers-in-cmake-install-includedir-and-a-subdirectory-thereof/15525/6 "2026-03-05T09:14:00Z")

</div>

Thanks everyone for your help. I am now using FILE\_SET while also setting `MACOSX_PACKAGE_LOCATION` for the files. I am fortunate that I had only 2 header directories to deal with and therefore only 2 `set_source_files_properties()` commands. I had to put `if(NOT <framework>)` around the code adding the file set to the target to stop CMake raising the error that FILE\_SETS are not allowed in frameworks.

Why is a PUBLIC FILE\_SET of type HEADERS not handled the same way as the PUBLIC\_HEADER item in an install command? It would make things easier.
