2025-05-16

Source file pathnames in MSBuild projects

PEARL: The source file pathnames that the compiler generates via the CallerFileName attribute (and also wrtes in .pdb files, a.k.a. symbol files) are absolute pathnames, meaning that they start from the drive letter, and they include the path to the developer's home folder and the kitchen sink.

This causes the following problems:

  1. The source file pathnames are only meaningful on the filesystem of the computer on which the code was compiled.
  2. The generated .pdb files can only be used on the computer on which the code was compiled.
  3. The source file pathnames embedded in the binaries disseminate sensitive information, such as the developer's username, to the whole wild world.
  4. The source file pathnames are too darn long, wasting precious real estate in the debug output window, where we have to prefix each log line with the source file pathname so that the line is clickable. 

The last problem could be remedied programmatically, by converting each source file pathname to a relative pathname before emitting it to the debug output window, (and it would work, with a caveat addressed below,) but the full source pathnames would still be stored in the executable and in the .pdb files, so this remedy would only address the last problem; the other problems remain.

The C# compiler supports a little-known property called 'PathMap', which can be used to fix this.

See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/advanced#pathmap

(Note that Microsoft calls it an "advanced" compiler option.)

PEARL: The documentation for the `PathMap` compiler option starts with a conspicuous note which says:

Note: Specifying PathMap prevents breakpoints from working in local debug builds. Only set PathMap for production or continuous integration builds.

This note is wrong, first of all because if you mess up the mapping, it is not only breakpoints that will stop working; all kinds of weird things will start happening while debugging, such as Visual Studio opening up a file dialog asking you to locate the source files for various lines of code.

Most importantly, this note is wrong because `PathMap` can be used to point to the exact same files by relative instead of absolute pathname, and everything continues to work just fine. (This is what the tooling should have been doing by default in the first place.) Problems arise only if `PathMap` is used to map files to locations that do not exist in the file system, but then the note is exactly as useless as a note saying that if you point a gun to your foot and pull the trigger, you will be shooting yourself in the foot.

Whether you shorten source file pathnames with code at runtime, or use PathMap to shorten them at compile-time, you have to be aware of another pitfall:

PEARL: Clicking on a line prefixed with a source file pathname in the output window does not work if the pathname is relative to the solution directory; Visual Studio only understands relative pathnames if they are relative to the directory of the startup project! This poses a serious problem considering that a solution may contain multiple executable projects, so the directory to which source file pathnames must be relative to changes depending on which project we launch.

At first this problem seems insurmountable, but luckily it can be solved, too.

To properly map source file pathnames from absolute to relative:

  1. Make sure that all of your project directories are immediate children of the solution directory.
  2. Make sure that all of your project names are identical to their respective directory names.
  3. Map each absolute source file pathname by replacing the `$(MSBuildProjectDirectory)` part with `./../$(MSBuildProjectName)`.

 


 

Cover image: The MSBuild logo from https://github.com/dotnet/msbuild/tree/main/branding

No comments:

Post a Comment