Creating a relocatable ProjectConfig.cmake when build uses absolute paths

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Creating a relocatable ProjectConfig.cmake when build uses absolute paths

Lucas Soltic
Hello,

I'm trying to create a relocatable package configuration file but I'm having a hard time with absolute paths that are used during the build.
Note that I use CMake 3.10.0.

First of all for the include path I'm using this:
target_include_directories(MyStaticTarget PUBLIC
                           $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
                           $<INSTALL_INTERFACE:include>)

The doc of BUILD_INTERFACE says: "Content of ... when the property is exported using export(), or when the target is used by another target in the same buildsystem. Expands to the empty string otherwise."
So according to the doc, when compiling the target MyTarget, the include dir I gave should not be used. Which seems consistent with the other uses of INTERFACE wording in CMake language. But it actually behaves like PUBLIC rather than INTERFACE. This is fine to me but inconsistent… :)
When exporting the target, the include dir uses what's given in for INSTALL_INTERFACE so it works fine. No problem on that part.


The second point is about libraries to link when using my project. My project exports a static library that depends on other libraries. These libraries are found (when generating my project through cmake) through calls like find_package(OpenGL). This provides a variable that contains an absolute non-relocatable path.

I've tried doing things like this:
target_link_libraries(MyStaticTarget PRIVATE
                      $<BUILD_INTERFACE:${OpenGL_absolute_path}>
                      $<INSTALL_INTERFACE:${OpenGL_relocatable_link_flag}>)

According to https://cmake.org/pipermail/cmake/2016-May/063400.html, for static libraries, PRIVATE link dependencies become "PUBLIC", which is ok. And I could see that the link flag for this "private" dependency got exported in the generated config. But the problem is that whatever I put for INSTALL_INTERFACE, it is ignored and not written to the generated cmake config file. The generated CMake config file just takes into account what I put for BUILD_INTERFACE. Contrary to what happened with include directories. And I don't want to use only ${OpenGL_relocatable_link_flag} because MyStaticTarget can be configured to be a dynamic library, in which case the library is really used during link and really want to link against the exact library that's referenced.

How can I have ${OpenGL_relocatable_link_flag} be used in the generated cmake config file?
Any other solution that would make the generated config relocatable is also fine.

Best regards,
Lucas 

--

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake
Reply | Threaded
Open this post in threaded view
|

Re: Creating a relocatable ProjectConfig.cmake when build uses absolute paths

Craig Scott-3
(Includes earlier reply from me not sent to the list)


On Fri, Jan 12, 2018 at 9:10 AM, Lucas Soltic <[hidden email]> wrote:
On Thu, Jan 11, 2018 at 10:09 AM, Lucas Soltic <[hidden email]> wrote:
Hello,

I'm trying to create a relocatable package configuration file but I'm having a hard time with absolute paths that are used during the build.
Note that I use CMake 3.10.0.

First of all for the include path I'm using this:
target_include_directories(MyStaticTarget PUBLIC
                           $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
                           $<INSTALL_INTERFACE:include>)

The doc of BUILD_INTERFACE says: "Content of ... when the property is exported using export(), or when the target is used by another target in the same buildsystem. Expands to the empty string otherwise."

I'm with you to here.
 
So according to the doc, when compiling the target MyTarget, the include dir I gave should not be used. Which seems consistent with the other uses of INTERFACE wording in CMake language. But it actually behaves like PUBLIC rather than INTERFACE. This is fine to me but inconsistent… :)

This part is less clear. What is MyTarget? Is it something you link to MyStaticTarget? If so, then the include dir should be used. In the target_include_directories() call above, the content of the generator expressions will both be treated as PUBLIC if they expand to something non-empty. Don't be confused by the word INTERFACE in the generator expressions, it is the PUBLIC before them that is controlling the visibility/transitivity of these include paths. Think of BUILD_INTERFACE as meaning "only when I'm building with this thing" and INSTALL_INTERFACE as "only when this thing is installed (i.e. put this in an export file but don't use it in the build I created it in)".

Arg I messed up my original e-mail. "MyTarget" is the same as "MyStaticTarget". I just didn't rename everywhere…
I rather have a clear vision about what INTERFACE, PUBLIC and PRIVATE keywords mean when used in target_...() commands so I understand that my paths will be used both by MyStaticTarget and targets that depend on it. It's just that BUILD_INTERFACE's documentation is misleading. Apart from that I deduced the same as what you said :)


The second point is about libraries to link when using my project. My project exports a static library that depends on other libraries. These libraries are found (when generating my project through cmake) through calls like find_package(OpenGL). This provides a variable that contains an absolute non-relocatable path.

I've tried doing things like this:
target_link_libraries(MyStaticTarget PRIVATE
                      $<BUILD_INTERFACE:${OpenGL_absolute_path}>
                      $<INSTALL_INTERFACE:${OpenGL_relocatable_link_flag}>)

According to https://cmake.org/pipermail/cmake/2016-May/063400.html, for static libraries, PRIVATE link dependencies become "PUBLIC", which is ok.

They only sort-of become PUBLIC. Only the linking part gets promoted to PUBLIC, not things like include paths, compiler defines or compiler options.

Indeed. So far so good :)


 
And I could see that the link flag for this "private" dependency got exported in the generated config. 


Short version: I think if you use the import libraries that the FindOpenGL module provides rather than the variables, you will have more success.

Long version (this is going to get quite involved, sorry): If your static library relies on an external library and you want that external dependency to be part of the exported definition of your target, then you'll have to do some extra work in how you provide your installed package. If you are using install(EXPORT) to produce files that other projects pick up via find_package(), then you probably want to be using find_dependency() from the CMakeFindDependencyMacromodule from inside your package's XXXConfig.cmake file. This will define the imported targets that your package's import target should, I believe, be expecting to exist and rely on as an interface dependency. Sketching it out, it might look something like this (untested, tired brain, so no guarantees!):

# Contents of XXXConfig.cmake:
include(CMakeFindDependencyMacro)
find_dependency(OpenGL)   # Makes sure the OpenGL::GL import target exists
include(XXXexports)       # The file you are probably already creating now for your package

I think the exported target will already have OpenGL::GL in the list of interface libraries for the MyStaticTarget import target created by XXXexports in the above, but if not it should be easy enough to add that to the end of the XXXConfig.cmake sample code (maybe a call to target_link_libraries() or set_property(TARGET MyStaticTarget...)).

Back in your original project, the target_link_libraries() call should be simplified down to:

find_package(OpenGL REQUIRED)
target_link_libraries(MyStaticTarget PRIVATE OpenGL::GL)

The MyStaticTarget will then have a link dependency on OpenGL::GL in both the build tree and when installed. It's very likely I've made errors in the above, but hopefully there's enough there for you to find your way to a solution.

I like this solution, but I can't use it as is. For 2 reasons: OpenGL was one example, but I also have other dependencies that don't provide an imported target, and the second reason is that this imported target for OpenGL is only available since CMake 3.8 and I must support at least up to 3.5.

Also I said "as is" because this makes me think that I could potentially define, in my project, an IMPORTED or INTERFACE library for OpenGL that would be used in a target_link_libraries() in my project. And in <Project>Config.cmake it would allow me to make use of the find_dependency() you suggested to create again this target.

Indeed. At the point where you call find_package(OpenGL), you could then test for the existence of the OpenGL::GL library and if it doesn't exist, you can define it yourself. This should allow you to use the import library with CMake versions earlier than the one where the OpenGL import library support was added.


 

The only downside I can see with this is that the <Project>Config.cmake file will need to be kept synchronized with the dependencies that are used in various places in my project, instead of having this done automatically (and thus always synchronized).

Pretty much. I think there have been some discussions in the issue tracker and/or merge requests over the past year related to how to propagate dependencies similar to this topic. It's a difficult area with no easy solution unfortunately.

 


But the problem is that whatever I put for INSTALL_INTERFACE, it is ignored and not written to the generated cmake config file. The generated CMake config file just takes into account what I put for BUILD_INTERFACE. Contrary to what happened with include directories.

This sounds strange. Can you provide a simple, complete project that reproduces the problem? If so, please open an issue in the bug tracker and attach it.

I reproduced it with a minimal example, and found out what is causing the issue.
At the beginning of the project, if I put cmake_minimum_required(VERSION 2.8.3) (which is what the project I'm working in currently uses) then in the generated config, only what's specified in BUILD_INTERFACE generator expression is taken into account.

If I change the requirement to version 3.0, the generated config file contains what I specified for INSTALL_INTERFACE generator expression.
I never realized that this command had an effect on CMake policies. At least now everything makes sense :)



--
Craig Scott
Melbourne, Australia

--

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
https://cmake.org/mailman/listinfo/cmake