Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set runtime PATH for CMake custom command on Windows

Tags:

I'm trying to port a *nix, CMake-based project to Windows. One header file needed by the main library is generated by a custom program, so the CMakeLists.txt file contains something like this:

add_executable(TableGenerator "TableGenerator.cpp") target_link_libraries(TableGenerator ${LibFoo_LIBRARY})  add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                    COMMAND TableGenerator "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                    DEPENDS TableGenerator) 

An important detail is that TableGenerator uses the external shared library LibFoo. For example under Linux, everything works fine, because libfoo.so is installed in one of the system library directories like /usr/local/lib, or CMake even sets the rpath attribute in the executable, saying where exactly to find the library.

On Windows, however, these kind of libraries are usually not installed into the system but are rather just extracted or compiled into some arbitrary directory in or near the build tree. In order for TableGenerator to run, the foo.dll would need to be available in or copied to one of the Dynamic-Link Library Search Order paths (say %WINDIR%\System32 or the build output directory for TableGenerator), which is not desirable.

How can I set the PATH environment variable for the custom command, i.e. to be used not during the CMake run but during the actual custom build step runtime?

like image 604
Yirkha Avatar asked Feb 16 '15 00:02

Yirkha


People also ask

How do I change my path in cmake?

CMake will use whatever path the running CMake executable is in. Furthermore, it may get confused if you switch paths between runs without clearing the cache. So what you have to do is simply instead of running cmake <path_to_src> from the command line, run ~/usr/cmake-path/bin/cmake <path_to_src> .

How do I run a command in cmake?

From the command line, cmake can be run as an interactive question and answer session or as a non-interactive program. To run in interactive mode, just pass the option “-i” to cmake. This will cause cmake to ask you to enter a value for each value in the cache file for the project.


2 Answers

While still doing my research in order to ask the question properly, I have found three solutions. Considering how hard it was to find this information, I decided to post the question and answer here anyway.


1. Using global variable CMAKE_MSVCIDE_RUN_PATH

There is a special variable dedicated to solving this exact problem – CMAKE_MSVCIDE_RUN_PATH. If set, it results in a line like this being added to the custom build step script:

set PATH=<CMAKE_MSVCIDE_RUN_PATH>;%PATH% 

So all that's needed then is something like this at a good place:

set(CMAKE_MSVCIDE_RUN_PATH ${LibFoo_RUNTIME_LIBRARY_DIRS}) 

I have originally noticed this variable only in CMake sources, because it used to be undocumented until CMake 3.10. So you might not be able to find it in documentation for older versions of CMake, but don't worry, it's been supported since 2006.

Advantages:
▪ Can be enabled at one central place
▪ No change at all in any of the add_custom_command() commands elsewhere is needed
▪ Only the path itself is set, no batch commands need to be written explicitly
▪ The obvious choice with clear name and intent

Disadvantages:
▪ Global for the whole CMake project and all custom commands
▪ Works with the "Visual Studio 9 2008" and above generators only


2. Setting the PATH explicitly using two COMMAND parameters

The script being generated for the custom build step in Visual Studio contains some prologue, then the commands themselves and then some epilogue. Wouldn't it be possible to simply add set PATH=... before the real command through another COMMAND parameter?

The documentation for add_custom_command() says:

COMMAND
Specify the command-line(s) to execute at build time. If more than one COMMAND is specified they will be executed in order, but not necessarily composed into a stateful shell or batch script.

So no, that's not guaranteed to be possible. But the Visual Studio project generator actually does it like this, i.e. the individual commands are just appended one after another, so the following does the job:

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                    COMMAND set "PATH=${LibFoo_RUNTIME_LIBRARY_DIRS};%PATH%"                    COMMAND TableGenerator "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                    DEPENDS TableGenerator) 

Advantages:
▪ The PATH can be changed for each custom command explicitly

Disadvantages:
▪ Relies on an undocumented behavior of the generator
▪ It's necessary to rewrite the whole command for Windows and keep both versions in sync
▪ Each custom command must be changed explicitly


3. Using file(GENERATE ...) to create a custom script

The documentation for add_custom_command() quoted above continues:

To run a full script, use the configure_file() command or the file(GENERATE) command to create it, and then specify a COMMAND to launch it.

This is a bit messy because of the additional temporary files and commands:

file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/RunTableGenerator.cmd"               CONTENT "set PATH=${LibFoo_RUNTIME_LIBRARY_DIRS};%PATH%                        %1 ${CMAKE_CURRENT_BINARY_DIR}/Table.h") add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                    COMMAND "${CMAKE_CURRENT_BINARY_DIR}/RunTableGenerator.cmd" "$<TARGET_FILE:TableGenerator>"                    DEPENDS TableGenerator) 

Notice the awkward way of sending the path to the executable as an argument. This is necessary because the script is writen once, but TableGenerator might be in different locations for different configurations (debug and release). If the generator expression was used directly in the content, a CMake error would be printed and the project would not build correctly for all but one configuration.

Advantages:
▪ The PATH can be changed for each custom command explicitly
▪ A fully documented and recommended solution

Disadvantages:
▪ Very noisy in the CMakefiles
▪ It's necessary to rewrite the whole command for Windows and keep both versions in sync
▪ Each custom command must be changed explicitly


4. Launch the custom command through CMake wrapper

See the other answer below contributed by Dvir Yitzchaki.


I had personally settled on the solution #1 because it was clean and simple, even before it got properly documented and supported by CMake in version 3.10. It should be the best way forward for you as well, unless you need to do something even more special.

like image 50
Yirkha Avatar answered Sep 29 '22 10:09

Yirkha


There is another way besides what Yirkha wrote and that is to run the executable through cmake and use cmake's -E option to set the environment.

So in your case it will be:

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                COMMAND ${CMAKE_COMMAND} -E env "PATH=${LibFoo_RUNTIME_LIBRARY_DIRS}" $<TARGET_FILE:TableGenerator> "${CMAKE_CURRENT_BINARY_DIR}/Table.h"                DEPENDS TableGenerator) 

See http://www.cmake.org/pipermail/cmake/2006-March/008522.html for details.

like image 24
Dvir Yitzchaki Avatar answered Sep 29 '22 12:09

Dvir Yitzchaki