Does the target_include_directories() command strip out duplicate paths added?

Yes, it does (BUT as good it can); see the following exemple project:

cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(test C)

file(WRITE ${CMAKE_BINARY_DIR}/main.c "int main(void){return 0;}\n")
add_executable(main main.c)

get_filename_component(SRC ${CMAKE_SOURCE_DIR}/../test REALPATH  CACHE)
get_filename_component(BIN ${CMAKE_BINARY_DIR} REALPATH  CACHE)
message("SRC=${SRC}")
message("BIN=${BIN}")

target_include_directories(main
    PRIVATE . ..
    ${CMAKE_BINARY_DIR}
    ${CMAKE_SOURCE_DIR}
    ${SRC}
    ${BIN}
)
test/build$ ninja -v
[0/1] /usr/bin/cmake.exe -S/e/workspace/cpp/test -B/e/workspace/cpp/test/build
SRC=/e/workspace/cpp/test
BIN=/e/workspace/cpp/test/build
-- Configuring done
-- Generating done
-- Build files have been written to: /e/workspace/cpp/test/build
[1/2] /usr/bin/cc.exe -I../. -I../.. -I. -I../ -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o CMakeFiles/main.dir/main.c.o   -c main.c
[2/2] : && /usr/bin/cc.exe   -Wl,--enable-auto-import CMakeFiles/main.dir/main.c.o  -o main.exe -Wl,--out-implib,libmain.dll.a -Wl,--major-image-version,0,--minor-image-version,0   && :

Part of that is the Ninja generator normalizing many paths. The other is that CMake deduplicates include paths by textual equivalence (possibly with a trailing / being ignored). Anything else would generally result in a lot more work being done at generate time.

I don’t recommend using . and .. in your include directories; the working directory of the compiler is different for different generators, so they don’t mean the same thing. I think using absolute paths here would generally make things more readable and be closer to what you want.

It shouldn’t matter. The target_include_directories() command internally converts relative paths to absolute before storing them in the INCLUDE_DIRECTORIES target property. It treats relative paths as being relative to the current source directory.

The . and … was only used to show the problem.
IMHO: converting too realpath while generating would be better and not to expensive.
It whould reduce the build time too.

Interesting is the result if the build dir is not part of source dir.

../.build$ ninja -v
[0/1] /usr/bin/cmake.exe -S/e/workspace/cpp/test -B/e/workspace/cpp/.build
SRC=/e/workspace/cpp/test
BIN=/e/workspace/cpp/.build
-- Configuring done
-- Generating done
-- Build files have been written to: /e/workspace/cpp/.build
[1/2] /usr/bin/cc.exe  -I/e/workspace/cpp/test/. -I/e/workspace/cpp/test/.. -I. -I/e/workspace/cpp/test  -MD -MT CMakeFiles/main.dir/main.c.o -MF CMakeFiles/main.dir/main.c.o.d -o CMakeFiles/main.dir/main.c.o   -c main.c
[2/2] : && /usr/bin/cc.exe   -Wl,--enable-auto-import CMakeFiles/main.dir/main.c.o  -o main.exe -Wl,--out-implib,libmain.dll.a -Wl,--major-image-version,0,--minor-image-version,0   && :

I wonder. REALPATH has other effects that may be undesirable. For instance, it also resolves symlinks, so if there are two include paths and one is symlinked to the other, that one will be eliminated. Which is probably fine… except when it isn’t.

Ultimately, is it CMake’s job to be pruning directories out of the include list at generate time, when those directories are meant to be consumed at build time? There could be situations where paths that are listed in target_include_directories() will end up looking different during the build process than they do when generating. CMake trying to be too smart could cause problems for some users.

I mean, ultimately, what you point to as a “problem” is only a problem because you actually specified the same directory multiple times, in different forms. CMake merely passed along the parameters you gave it to the build system. Perhaps the right answer is simply, “Don’t do that.”?

I do not want to use “.” and “…”

But sompe people/code generators (i.e. Ninja) do!

I want that cmake generators normalize include directories with i.e. unix realpath:

NAME
       realpath - print the resolved path

SYNOPSIS
       realpath [OPTION]... FILE...

DESCRIPTION
       Print the resolved absolute file name; all but the last component must exist

       -e, --canonicalize-existing
              all components of the path must exist

       -m, --canonicalize-missing
              no path components need exist or be a directory

       -L, --logical
              resolve '..' components before symlinks

       -P, --physical
              resolve symlinks as encountered (default)

       -q, --quiet
              suppress most error messages

       --relative-to=DIR
              print the resolved path relative to DIR

       --relative-base=DIR
              print absolute paths unless paths below DIR

       -s, --strip, --no-symlinks
              don't expand symlinks

       -z, --zero
              end each output line with NUL, not newline

       --help display this help and exit

       --version
              output version information and exit

Then this issue is the one you want. It looks like using absolute paths confuses ninja’s depslog, so it cannot be done unconditionally right now.

I have tried to fix ninja generator, but now the BuildDepends ctests fails for ninja?

klein_cl@2d-oehsdg:~/Workspace/cpp/cmake$ git diff
diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h
index 88194b7a29..25cfce232d 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -144,7 +144,7 @@ public:
   std::string GetIncludeFlags(const std::vector<std::string>& includes,
                               cmGeneratorTarget* target,
                               const std::string& lang,
-                              bool forceFullPaths = false,
+                              bool forceFullPaths = true,
                               bool forResponseFile = false,
                               const std::string& config = "");
 
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 3803621fd1..236e682d50 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -211,8 +211,8 @@ void cmNinjaTargetGenerator::AddIncludeFlags(std::string& languageFlags,
   // Add include directory flags.
   std::string includeFlags = this->LocalGenerator->GetIncludeFlags(
     includes, this->GeneratorTarget, language,
-    language == "RC", // full include paths for RC needed by cmcldeps
-    false, config);
+    true, //XXX language == "RC", // full include paths for RC needed by cmcldeps
+    false, config);  //TODO: why not forceFullPaths? CK
   if (this->GetGlobalGenerator()->IsGCCOnWindows()) {
     std::replace(includeFlags.begin(), includeFlags.end(), '\\', '/');
   }
@@ -1155,8 +1155,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
         cmSystemTools::GetParentDirectory(source->GetFullPath()));
 
       std::string sourceDirectoryFlag = this->LocalGenerator->GetIncludeFlags(
-        sourceDirectory, this->GeneratorTarget, language, false, false,
-        config);
+        sourceDirectory, this->GeneratorTarget, language, true, false,
+        config);    // TODO Why not forceFullpath? CK
 
       vars["INCLUDES"] = cmStrCat(sourceDirectoryFlag, ' ', vars["INCLUDES"]);
     }
klein_cl@2d-oehsdg:~/Workspace/cpp/cmake$ 

Yes. That’s why the issue is still open. The ninja tool itself does not support us writing absolute paths right now. https://github.com/ninja-build/ninja/issues/1251

Ok, I can live with the workaround to have the binary_dir outsite source_dir.

But this show, that the realpath() sould be used too!
and auto end = cmRemoveDuplicates(allIncludeDirs);

target_include_directories(main
    PRIVATE . .. /dir1 /dir2 /dir1 /dir2 /dir1/../dir2 /dir2/../dir1
)

realpath has some…issues in CMake due to an old feature to support an uncommon use case that affects too much. It’s on our list to reduce its scope. https://gitlab.kitware.com/cmake/cmake/issues/16228

(Cc: @brad.king)

Thanks for your support