0

I load a path name with cmake

add_definitions(-DMY_PATH =${CMAKE_INSTALL_FULL_DATADIR}/path)

and want to use as a string in my C++ program to load some data. For this the stringification operator # is really handy - I use the macro provided in this answer which is the same as here. Now when I have "linux" or "unix" in my path, this goes horribly wrong (at least with gcc), as these names are simply replaced by "1":

#include <stdio.h>

#define xstr(a) str(a)
#define str(a) #a

#ifndef MY_PATH
    #define MY_PATH /path/x86-unix/linux/path
#endif

int main()
{   
    char my_path_str[] = xstr(MY_PATH);
    printf("my path is %s", my_path_str);

    return 0;
}

Try it out

Can anyone give a hint why is this happening and how I can prevent it? There is a similar question here, but there is no suitable answer to use it with cmake.

yar
  • 1,855
  • 13
  • 26
  • It goes "horribly wrong" when you use a *macro* in your path. Always use strings for your paths, even when you define them as macros. – Some programmer dude Jul 28 '21 at 09:21
  • @Someprogrammerdude This is a problem if you use windows paths, as then backslashes are treated as escape characters. The # operator takes this into account – yar Jul 28 '21 at 09:24
  • `I load a path name with cmake and want to use as a string` _How_ do you "load a path with cmake"? Please show cmake code. Do you want to use `configure_file`? Did you read https://cmake.org/cmake/help/latest/command/configure_file.html and inspected example section with an example of a string? Are you asking XY question? – KamilCuk Jul 28 '21 at 09:25
  • But it brings more problems that are harder to solve (otherwise you wouldn't be asking here, right? ;) ). And even most beginners knows that backslashes needs to be escaped. Besides, Windows support forward slashes for normal paths anyway. – Some programmer dude Jul 28 '21 at 09:27
  • @Someprogrammerdude yes, it does. But if the user provides a path with backward slashes I have a problem. Plus cmake also grabs paths with backslashes when used on windows. – yar Jul 28 '21 at 09:39
  • Another problem: What is the path have spaces in it? Quite common on Windows systems. And for "user input" then if the "users" Are modifying the build scripts then they should know about escaping. For all other run-time input such strings aren't processed and doesn't need escapes. – Some programmer dude Jul 28 '21 at 09:44

2 Answers2

2

In your case, using quotes around the path that you define will suffice:

#define MY_PATH "/path/x86-unix/linux/path"

This will give the expected output (though this contains quotes on both the sides). The reason why linux and unix are substituted with 1, is because they are already defined as macros. If you want the path without any quotes, you could undef unix and linux beforehand:

#undef unix
#undef linux
#define MY_PATH /path/x86-unix/linux/path

But this might cause some unexpected problems when other libraries check for the unix or the linux macro. You can use #pragma push_macro to prevent macro substitution (as explained in this answer), but as you would expect, it is not very portable.

As for Windows paths, it seems to work with escaping the backslash where required:

#define MY_PATH "C:\\Program Files\\Path\\Folder"

This gives the expected output.

FakeMod
  • 125
  • 1
  • 7
1

why is this happening

Macros unix and linux are legacy defined to 1 on UNIX platforms.

Part of the program /path/x86-unix/linux/pat consists of tokens unix and linux, so as part of macro expansion in xstr these macros are substituted for 1.

how I can prevent it?

#undef linux and unix macros. Or disable gnu extensions, for example, use c11 standard. With GCC compiler on Linux:

$ gcc  -E -dM - </dev/null | grep linux
#define __linux 1
#define __gnu_linux__ 1
#define linux 1  // here it is
#define __linux__ 1
$ gcc -std=c11 -E -dM - </dev/null | grep linux
#define __linux 1
#define __gnu_linux__ 1
#define __linux__ 1
// now there's no #define linux

how I can prevent it?

I load a path name with cmake and want to use as a string in my C++ program

Adapting example from Example section from CMake documentation of configure_file, create a file named my_path.h.in with content:

#cmakedefine MY_PATH "@MY_PATH@"

Add the following to your cmake configuration:

set(MY_PATH "/path/x86-unix/linux/path") 
# Generate the file
configure_file(my_path.h.in
    ${CMAKE_CURRENT_BINARY_DIR}/generated/my_path.h
    ESCAPE_QUOTES
    @ONLY
)
# add the path to your target
target_include_directories(your_target
    PUBLIC_or_PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}/generated
)

And use #include <my_path.h> in your program to get MY_PATH definitions.

I see no reason to use a macro - maybe it would be better to do static const char MY_PATH[] = "@MY_PATH@"; instead.


I load a path name with cmake

 add_definitions(-DMY_PATH =${CMAKE_INSTALL_FULL_DATADIR}/path)

Do not use add_definitions. Prefer target_compile_definitions instead. See CMake add_definitions documentations.

As a crude workaround, you can add quotes, assuming the shell and compiler will properly parse them:

target_compile_definitions(your_target PRIVATE 
    MY_PATH="${CMAKE_INSTALL_FULL_DATADIR}/path"
)

(Note that quotes in the above are preserved literally and passed to the compiler (or build system). CMake quoting does not work like shell quoting. In CMake language, to quote a word, quotes " have to be exactly the first and last characters of the word. If one of them is in the middle, they are interpreted literally)

However, I think using configure_file would be preferred, because of ESCAPE_QUOTES.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111