cmake_path(ABSOLUTE_PATH ...) vs. get_filename_component(... ABSOLUTE)

I tried to use cmake_path() instead of get_filename_component() to “normalize” a path. But when I start with "/x/y/z/w/../.." I get these results:

-- cmake_path:             /x/y/
-- get_filename_component: /x/y

Is there any way I can get rid of the trailing “/” in the cmake_path() case?
I don’t know if I’m missing some way to get cmake_path() to do the same as get_filename_component()?

And is this an intentional difference, or just a consequence of using lexically_normal() from the <filesystem> header in recent C++ versions?

My example script looks like:

set(aaa /x/y/z/w/../..)
cmake_path(ABSOLUTE_PATH aaa NORMALIZE)
message(STATUS "cmake_path: ${aaa}")

set(aaa /x/y/z/w/../..)
get_filename_component(res "${aaa}" ABSOLUTE)
message(STATUS "get_filename_component: ${res}")

Regards,
Johan Holmberg

Cc: @marc.chevrier

The trailing ‘/’ is here on purpose. It enables to distinguish between a directory name and file name.

cmake_path has a strong semantic over paths, so they are not handled exactly in the same way as get_filename_component.

There is no way to remove it. What for do you want to do that, anyway?

Our CMake code has many places where new paths are used/created “inline” in the code like "${MY_DIR}/my_subdir". If MY_DIR is the result of a call to cmake_path() like I described earlier, will not the resulting path look like "/x/y//my_subdir" ? Maybe this works and the double “//” is harmless, but is it always so? Even if CMake itself handles this, what about if for example custom commands get such a path?

As I understand, all variables in CMake are still strings in the end. And having to think in each case if a variable representing a directory has a trailing “/” or not seems doomed to cause trouble.

The documentation said that get_filename_component() is "superseded by cmake_path()", so I thought that I should move aways from using the old function. But until I understand how to handle this “//” situation, I think I will have to stick with get_filename_component(). Rewriting all code to use cmake_path() seems like too much work with an existing codebase.

Having a double ‘/’ in paths is completely harmless in CMake.

For custom commands, on Unix and Unix like systems, I am not aware of tools having problems with such paths. On Windows, it could be more problematic, but anyway, the right thing to do is to transform the CMake path to native path to avoid any trouble (use cmake_path(NATIVE_PATH) or cmake_path(CONVERT ... TO_NATIVE_PATH_LIST) commands for that purpose).

rsync doesn’t care about inner double / situations, but a trailing one does change how it works. Other tools act this way too (primarily when it is a directory symlink in the “target” position for various Unix tools).

And how the behavior is changed ?

rsync treats trailing-/ paths to mean “contents of” rather than the directory itself.

rsync -r foo/ bar will copy foo's contents into bar while rsync -r foo bar will create bar/foo. Playing around a bit, the “target” position isn’t such an easy rule, so it really depends on the tool being used there it seems.

I see. What a weird idea to change the semantics of the path!!

I agree, with such approaches in the wild, it is required to be very attentive of arguments delivered to third party tools…

This is surprising at first but very useful: it helps to make rsync idempotent/deterministic.

If B does not exist yet and you cp -r A B twice then the first and second invocation will do two different things… on some operating systems. On other operating systems it is idempotent. It’s a mess.

The trailing slash is rsync’s way to force you to say exactly which one you want.

I stopped using cp -r a long time ago, I only use rsync now.