tinman
tinman

Reputation: 6608

How do I configure MSVC to show relative path for header files using __FILE__?

I recently discovered that when using the __FILE__ predefined macro in MSVC (specifically 2013) that by default it will print relative paths for source files and absolute paths for header files.

As an example I have a VS project containing the following:

Solution
    Project
        Headers
            foo.h
        Sources
            main.cpp

Both main.cpp and foo.h are in the same directory on disk.

main.cpp:

#include <iostream>
#include <string>

#include "foo.h"

int main(int, char*[])
{
    std::cout << __FILE__ << std::endl;
    foo::bar();

    std::cout << "Press enter to exit";
    std::string str;
    std::getline(std::cin, str);

    return 0;
}

foo.h:

#ifndef FOO_H
#define FOO_H

#include <iosfwd>

class foo
{
public:
    static void bar()
    {
        std::cout << __FILE__ << std::endl;
    }
};

#endif

When I run the application (in release mode, with the default settings - compiling with /Zi and /FC is not defined) then the output is:

main.cpp
c:\users\<user>\documents\dev\solution\project\foo.h
Press enter to exit

I know I could probably pass in a base path and strip it out at runtime but I was wondering whether there was any way to change this behavior at compile time? Obviously defining /FC produces the opposite result and I cannot see anything else in the MSVC manual to control the display of header paths. I am thinking this might be a hardcoded behavior so that if the compiler is able to pick up two files called foo.h from different include paths that you can still distinguish between them or because it would be possible to have an include path unrelated to the base of the sources and displaying as relative would be messy.

Upvotes: 11

Views: 1583

Answers (1)

user541686
user541686

Reputation: 210725

I'll post my own solution, which is imperfect but the closest workaround I know of.

Every file this code is included in will strip off the PROJECT_DIR prefix from __FILE__ when assert is called, and thus the binary will not contain that prefix.

A better solution would probably wish to add ..\ prefixes as necessary, handle / as well, and possibly preserve any absolute paths that are on a different volume, but I'm not doing any of those here.

// Add this to your .vcxproj:
// <PreprocessorDefinitions>PROJECT_DIR="$(ProjectDir.Replace('\', '\\').TrimEnd('\'))";%(PreprocessorDefinitions)</PreprocessorDefinitions>

#include <cassert>
#include <type_traits>

#ifdef PROJECT_DIR
template<class Char1, class Char2>
constexpr size_t path_imismatch(Char1 const a[], Char2 const b[])
{
    size_t r = 0;
    size_t i = 0;
    for (;;)
    {
        Char1 ch1 = a[i];
        Char2 ch2 = b[i];
        bool const finished = ch1 == Char1() || ch2 == Char2() || !(ch1 == ch2);
        if (ch2 == Char2()) { ch2 = '\\'; }
        bool const isdirsep = ch1 == '\\' && ch1 == ch2;
        if (isdirsep && ch1 == ch2) { r = i + 1; }
        if (finished) { break; }
        ++i;
    }
    return r;
}
#define FILENAME(File, Root) ([]() { typedef std::decay_t<decltype(*(File))> _FileNameChar; constexpr _FileNameChar const f[] = File; enum : size_t { O = path_imismatch(f, Root), N = sizeof(f) / sizeof(*f) - O }; constexpr std::array<_FileNameChar, N> const result = []() { std::array<_FileNameChar, N> r = {}; for (size_t i = 0; i < N; ++i) { r[i] = f[O + i]; } return r; }(); return std::integral_constant<std::remove_const_t<decltype(result)>, result>(); }().value.data())
#define _wassert(Source, File, Line) (_wassert)(Source, FILENAME(File, _CRT_WIDE(PROJECT_DIR)), Line)
#endif

Upvotes: 0

Related Questions