# Does ENVIRONMENT\_MODIFICATION property support generator expression?

**URL:** https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442
**Category:** Usage
**Created:** [March 22, 2024, 3:31am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442 "2024-03-22T03:31:15Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![b1an](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/c2a13f/32.png) [@b1an](https://discourse.cmake.org/u/b1an)
#### Post date: [March 22, 2024, 3:31am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/1 "2024-03-22T03:31:15Z")

</div>

I’m using set\_tests\_properties to prepend a new directory in `$ENV{PATH}`. What I expect is this test case can find a imported target shared library.

```auto
set_tests_properties(normal_test
  PROPERTIES
    ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<PATH:GET_PARENT_PATH,$<SHELL_PATH:$<TARGET_PROPERTY:aaa,IMPORTED_LOCATION$<$<CONFIG:Debug>:_DEBUG>>>>"
)

```

The aaa is just a imported target name whose IMPORTED\_LOCATION and IMPORTED\_LOCATION\_DEBUG set to the relevant shared library.

But it does not work as I want it to.

I try to use `add_custom_target` and `${CMAKE_COMMAND} -E echo` to print that generator expression value. It’s correct.

So my question shows as title. And if not what is the right way to do it?

---

<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: [March 22, 2024, 10:19pm UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/2 "2024-03-22T22:19:58Z")

</div>

[This issue](https://gitlab.kitware.com/cmake/cmake/-/issues/23817) is relevant to your query. How are you defining your tests? Are you using the `add_test(NAME)` form?

---

<div class="post-metadata">

### Author: ![b1an](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/c2a13f/32.png) [@b1an](https://discourse.cmake.org/u/b1an)
#### Post date: [March 25, 2024, 2:40am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/3 "2024-03-25T02:40:22Z")

</div>

Yes. `add_test(NAME normal_test COMMAND)`.

---

<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: [March 26, 2024, 9:13pm UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/4 "2024-03-26T21:13:56Z")

</div>

> [@b1an](#):
>
> But it does not work as I want it to.

Please describe more fully what happens and how that is not working. It would also help if you provided a minimal, complete example that demonstrates the problem. Without that, it’s likely we’ll miss the underlying cause and not be able to work out what’s going on for you.

---

<div class="post-metadata">

### Author: ![b1an](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/c2a13f/32.png) [@b1an](https://discourse.cmake.org/u/b1an)
#### Post date: [March 27, 2024, 4:11am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/5 "2024-03-27T04:11:21Z")

</div>

What I want to do is let my test case can find the imported target shared library by adding imported target shared library path to `PATH` environment variable. I use `ENVIRONMENT_MODIFICATION` and `path_list_prepend` to modify environment variable and `$<TARGET_PROPERTY:bbb::bbb,IMPORTED_LOCATION$<$<CONFIG:Debug>:_DEBUG>` to get shared library full path in different build configuration.

1. I add a test case using `add_test(NAME normal_test COMMAND)` for executable target aaa.

```auto
add_test(NAME aaa_normal_test
  COMMAND aaa "${CMAKE_CURRENT_LIST_DIR}/test.data")

```

1. I modify env by `set_tests_properties`.

```auto
set_tests_properties (aaa_normal_test
  PROPERTIES
    ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<PATH:GET_PARENT_PATH,$<SHELL_PATH:$<TARGET_PROPERTY:bbb::bbb,IMPORTED_LOCATION$<$<CONFIG:Debug>:_DEBUG>>>>"
)

```

1. The aaa is just a executable target.

```auto
add_executable (aaa
  "main.cpp"
)

```

1. The bbb::bbb is an alias target.

```auto
add_library (bbb SHARED IMPORTED GLOBAL)

set_target_properties (bbb
  IMPORTED_LOCATION "${CMAKE_CURRENT_LIST_DIR}/lib/Release/x64/bbb.dll"
  IMPORTED_IMPLIB "${CMAKE_CURRENT_LIST_DIR}/lib/Release/x64/bbb.lib"
  IMPORTED_LOCATION_DEBUG "${CMAKE_CURRENT_LIST_DIR}/lib/Debug/x64/bbb.dll"
  IMPORTED_IMPLIB "${CMAKE_CURRENT_LIST_DIR}/lib/Debug/x64/bbb.lib"
  IMPORTED_CONFIGURATIONS "Release;Debug"
  MAP_IMPORTED_CONFIG_RELWITHDEBUGINFO Release
  MAP_IMPORTED_CONFIG_MINSIZEREL Release
)

target_include_directories (bbb
  INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include"
)

add_library (bbb::bbb ALIAS bbb)

install (IMPORTED_RUNTIME_ARTIFACTS bbb)

```

1. I also linked aaa with bbb.

```auto
target_link_libraries (aaa
  PRIVATE bbb::bbb
)

```

I expects that the test case should execute normally at least no matter passing or failing. But it says “can not find library bbb.dll” on Windows. I use procexp64.exe from SysinternalsSuite to check `PATH` and the imported target shared library path does not show up.

So, does generate expression support in ENVIRONMENT\_MODIFICATION? And It is the way to use a pre-built shared library on Windows? What if not, how to do it?

---

<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: [March 27, 2024, 8:46pm UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/6 "2024-03-27T20:46:54Z")

</div>

Thanks for the details, but what I was after was a _complete_ minimal example, i.e. a full `CMakeLists.txt` file and any source files it needs. Then I can (a) verify I get the same problem as you, (b) confirm that there’s not some hidden detail affecting the behavior.

From what you’ve shown, I can see this generator expression is malformed:

```cmake
set_tests_properties (aaa_normal_test
  PROPERTIES
    ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<PATH:GET_PARENT_PATH,$<SHELL_PATH:$<TARGET_PROPERTY:bbb::bbb,IMPORTED_LOCATION$<$<CONFIG:Debug>:_DEBUG>>>>"
)

```

The `$<PATH:GET_PARENT_PATH,...>` expression expects its argument to be in CMake’s path format, which is always forward slashes. But you’re providing a native shell path to it by using `$<SHELL_PATH:...>`. You need to reverse the order you apply those operations.

```cmake
set_tests_properties (aaa_normal_test
  PROPERTIES
    ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<SHELL_PATH:$<PATH:GET_PARENT_PATH,$<TARGET_PROPERTY:bbb::bbb,IMPORTED_LOCATION$<$<CONFIG:Debug>:_DEBUG>>>>"
)

```

You’re also assuming that `IMPORTED_LOCATION_DEBUG` is set when using the Debug config, but otherwise the `IMPORTED_LOCATION` is set. One of these might not be true. It is more common for imported targets to either set just `IMPORTED_LOCATION`, or to only set the `IMPORTED_LOCATION_xxx` config-specific properties. It is much less common to set both. EDIT: I didn’t read far enough, I can see that you are setting the properties on `bbb:bbb` too, and they are set consistent with how you’re using them.

---

<div class="post-metadata">

### Author: ![b1an](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/b/c2a13f/32.png) [@b1an](https://discourse.cmake.org/u/b1an)
#### Post date: [March 28, 2024, 5:05am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/7 "2024-03-28T05:05:20Z")

</div>

I made a test project to let you can reproduce it.

1. project tree

```auto
├─test
│ │ CMakeLists.txt
│ │ main.cpp
│ │
│ └─bbb
│ CMakeLists.txt
│
└─test_shared_lib
        CMakeLists.txt
        shared_lib_main.cpp
        shared_lib_main.h

```

1. steps to produce

2. build shared library

run commands to build shard library

```auto
cd ${SHARED_LIBRARY_ROOT}
cmake -S . -B build
cmake --build build --config Debug
cmake --install build --prefix install/Debug --config Debug
cmake --build build --config Release
cmake --install build --prefix install/Release --config Release

```

1. copy output to test dir

copy all content in install directory to `test/bbb`

and then copy `test/bbb/Relase/include` or `test/bbb/Relase/include` to `test/bbb`

1. build test binary and run ctest

**If all proceduces above are correct, the building should success.**

use visual studio to open test CMakeLists.txt and then build it.

run tests from TestExplorer.

1. files

- test/CMakeLists.txt

```auto
# test
#

cmake_minimum_required (VERSION 3.28)

project (test_project 
  VERSION 0.0.1
  LANGUAGES C CXX
)

add_subdirectory ("bbb")

include (CTest)

add_executable (test_main
  "main.cpp"
)

target_compile_features (test_main
  PRIVATE cxx_std_23
)

target_link_libraries (test_main
  PRIVATE bbb::bbb
)

if (${BUILD_TESTING})
  add_test (NAME normal_function_test_test_main
    COMMAND test_main
  )

  set_tests_properties (normal_function_test_test_main
    PROPERTIES 
	  ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<SHELL_PATH:$<PATH:GET_PARENT_PATH,$<TARGET_PROPERTY:bbb::bbb,IMPORTED_LOCATION$<IF:$<CONFIG:Debug>,_DEBUG,_RELEASE>>>>" 
  )
endif()

```

- test/main.cpp

```auto
// main.cpp
//

#include <shared_lib_main.h>

int main(int argc, char* argv[])
{
  int out = 0;
  int res = safe_add_two_integer(1, 2, &out);

  if (res == 0)
  {
    return 1;
  }
  return 0;
}

```

- bbb/CMakeLists.txt

```auto
# bbb
#

add_library (bbb SHARED IMPORTED GLOBAL)

set_target_properties (bbb PROPERTIES
  IMPORTED_LOCATION_RELEASE "${CMAKE_CURRENT_LIST_DIR}/Release/lib/test_shared_lib.dll"
  IMPORTED_IMPLIB_RELEASE "${CMAKE_CURRENT_LIST_DIR}/Release/lib/test_shared_lib.lib"
  IMPORTED_LOCATION_DEBUG "${CMAKE_CURRENT_LIST_DIR}/Debug/lib/test_shared_lib.dll"
  IMPORTED_IMPLIB_DEBUG "${CMAKE_CURRENT_LIST_DIR}/Debug/lib/test_shared_lib.lib"
  IMPORTED_CONFIGURATIONS "Release;Debug"
  MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
  MAP_IMPORTED_CONFIG_MINSIZEREL Release
)

target_include_directories (bbb 
  INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include"
)

add_library (bbb::bbb ALIAS bbb)

install (IMPORTED_RUNTIME_ARTIFACTS bbb)

```

- test\_shared\_lib/CMakeLists.txt

```auto
# test
#

# build and install commands:
# > cd ${PROJECT_ROOT}
# > cmake -S . -B build
# > cmake --build build --config Debug
# > cmake --install build --prefix install/Debug --config Debug
# > cmake --build build --config Release
# > cmake --install build --prefix install/Release --config Release

cmake_minimum_required (VERSION 3.23)

project (test_shared_lib_project 
  VERSION 0.0.1
  LANGUAGES C CXX
)

include (GenerateExportHeader)

add_library (test_shared_lib SHARED)

set_target_properties (test_shared_lib
  PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON
)

generate_export_header (test_shared_lib)

target_sources (test_shared_lib
  PRIVATE "shared_lib_main.cpp"
  PUBLIC FILE_SET public_headers TYPE HEADERS FILES "shared_lib_main.h"
)

install (TARGETS test_shared_lib 
  RUNTIME DESTINATION lib
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
  FILE_SET public_headers DESTINATION include
)
install (FILES "${PROJECT_BINARY_DIR}/test_shared_lib_export.h" TYPE INCLUDE)

```

- test\_shared\_lib/shared\_lib\_main.cpp

```auto
// main.cpp
//

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

int safe_add_two_integer(int a, int b, int* out)
{
  if (out == nullptr)
  {
    return 0;
  }

  if (a > ~b)
  {
    return 0;
  }

  *out = a + b;

  return 1;
}

#ifdef __cplusplus
}
#endif // __cplusplus

```

- test\_shared\_lib/shread\_lib\_main.h

```auto
// shared_lib_main.h
//

#pragma once

#include "test_shared_lib_export.h"

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

int TEST_SHARED_LIB_EXPORT safe_add_two_integer(int a, int b, int* out);

#ifdef __cplusplus
}
#endif // __cplusplus

```

---

<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: [April 13, 2024, 12:22am UTC](https://discourse.cmake.org/t/does-environment-modification-property-support-generator-expression/10442/8 "2024-04-13T00:22:04Z")

</div>

Thanks for your patience, it took me a while to find time to investigate this one.

The problem is due to your project, not the generator expression functionality. The way you’ve handled the symbol export has some errors. Firstly, you’re explicitly handling symbol visibility by using `generate_export_header()`, which is good. But then you also set the `WINDOWS_EXPORT_ALL_SYMBOLS` target property. Don’t set that property, you don’t need it and I wouldn’t be surprised if that is interfering with things.

The next error is you’ve misplaced the `TEST_SHARED_LIB_EXPORT` define in the `shared_lib_main.h` header. It should be this:

```auto
TEST_SHARED_LIB_EXPORT int safe_add_two_integer(int a, int b, int* out);

```

Note the `TEST_SHARED_LIB_EXPORT` comes before the `int` return type.

The main error though is that your `shared_lib_main.cpp` file doesn’t include the `shared_lib_main.h` header. As a consequence, when the compiler compiles `shared_lib_main.cpp`, it doesn’t see the symbol visibility annotation and the `safe_add_two_integer()` function doesn’t end up being exported in the resultant binary. Once you add a `#include <test_shared_lib.h>` to your `shared_lib_main.cpp`, you’ll then get an error that it can’t find `test_shared_lib_export.h`. You’ll need to add a line like the following to fix that:

```auto
target_include_directories(test_shared_lib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)

```

Switching to your `test` project, the `main.cpp` there will return an exit code of 1 as things are currently written. Therefore, when you run `ctest`, the test will fail, and it will be hard to distinguish that expected scenario from a situation where there was a problem starting the executable. I had to add some debug output and make it return 0 in all cases to be able to diagnose this one.

You can also verify that the PATH handling is correct by replacing your test executable with a command that runs a CMake script, and have that CMake script print out the value of `$ENV{PATH}`. When I did this, I was able to confirm the PATH was what we expect.
