For posterity: another of these CMake "hacks" — No CMAKE_C_COMPILER could be found.

So, CMake seems incapable of passing through /pdbaltpath:%_PDB% when given in a CMakeLists.txt (first learning). It somehow ends up as %%%%_PDB%%%% which then will evaluate — assuming a foobar.exe — to %%%foobar.pdb%%% in the PE file. Using generator expressions ($<1:...>) didn’t help either.

So obviously one would start using a workaround like this dumped into a Directory.Build.targets (because the corresponding .props ends up too early in the MSBuild sequencing, so we need to delay things a bit):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<Target Name="OverrideLinkerSwitches" AfterTargets="ComputeLinkSwitches" BeforeTargets="Link">
		<ItemGroup>
			<Link>
				<AdditionalOptions>%(Link.AdditionalOptions) /pdbaltpath:%_PDB%</AdditionalOptions>
			</Link>
		</ItemGroup>
	</Target>
</Project>

For those not “in the know” this manipulates the <ItemGroup /> named Link after it has been defined in the project file, if we assume a vanilla .vcxproj in order to append the linker switch to the end.

Well done, nothing to see here, move on.

Nope, not so! This is what trips up CompilerIdCXX.vcxproj (Modules/CMakeDetermineCXXCompiler.cmakeModules/CMakeFindBinUtils.cmake) with:

error MSB4096: The item “Debug\CMakeCXXCompilerId.obj” in item list “Link” does not define a value for metadata “AdditionalOptions”. In order to use this metadata, either qualify it by specifying %(Link.AdditionalOptions), or ensure that all items in this list define a value for this metadata.

Hmm, very interesting. Why would Link items not have this metadata which gets set by every vanilla C/C++ .vcxproj file?! And why did I even ask?

Of course wasted a massive amount of time on this, because the CMake generator step for VS does considerably more than is conventional in this ecosystem and generates throwaway projects with paths hardcoded to the system and the CMake version and other goodness. It’s the Unix ./configure bolted onto default VS builds for no particular reason.

At a minimum I would have expected that the underhanded detection mechanisms inhibit Directory.Build.* and friends (like with ImportDirectoryBuildProps=false and friends), so the developer can see the failure in the generated projects which then do include them, but the detection with “out-of-tree” inside subdirectory (build dir underneath source dir) doesn’t accidentally catch ambient MSBuild stuff during its “ssssshhhhh”-phase.

Instead I have to work around CMake’s hacks now with this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<ItemDefinitionGroup>
		<Link><!-- Bolt the metadata onto the Link items now, using whatever may have been set before -->
			<AdditionalOptions>%(AdditionalOptions)</AdditionalOptions>
		</Link>
	</ItemDefinitionGroup>
	<Target Name="OverrideLinkerSwitches" AfterTargets="ComputeLinkSwitches" BeforeTargets="Link">
		<ItemGroup>
			<Link>
				<AdditionalOptions>%(Link.AdditionalOptions) /pdbaltpath:%_PDB%</AdditionalOptions>
			</Link>
		</ItemGroup>
	</Target>
</Project>

If you can tell I am no big fan of CMake, that’s no coincidence. But at least this way others will know how to work around it.

At least I got to drive -Wdev --debug-find --debug-output --log-level=DEBUG again.

[imagine a hide the pain Harold GIF here]

PS: it doesn’t find either, so this thread is equally valid for CMAKE_CXX_COMPILER.

FYI: place this into the first <PropertyGroup /> in the .vcxproj and you’ll inhibit these:

    <ImportDirectoryBuildProps>false</ImportDirectoryBuildProps>
    <ImportDirectoryBuildTargets>false</ImportDirectoryBuildTargets>

However, there are more alike (e.g. ImportUserLocationsByWildcardBeforeMicrosoftCommonProps … use grep). MSBuild has many extension mechanisms. Perhaps a subst-drive to an otherwise empty directory would be better than this, because even %TEMP% isn’t safe from ambient MSBuild extension files.

Did you try using bracket syntax, like [[/pdbaltpath:%_PDB%]]

It’s not super clear to me what exactly the goal is here, just that this isn’t the best way to accomplish it

Hi. The goal is to remove all but the file name from the PDB path that gets imbued into the binary. Symbol servers and matching the symbols does the rest. No full paths needed (the normal way is to store the full path to the PDB on the build system). For this to work the %_PDB% must not expand prior to passing it to the linker.

%_PDB% gets internally set during linking with link.exe.

I haven’t tried [[/pdbaltpath:%_PDB%]] and will give it a shot. Thanks. You’re referring to these here, right?

Tested now. The outcome is /pdbaltpath:%%_PDB%% as opposed to four % on either side.

Then this indeed sounds like a CMake bug, or at least undocumented behavior. Bracket arguments are supposed to not perform any escaping. Maybe @brad.king can offer some help here?

Oh and here’s a hack for debugging what CMake is trying behind the scenes. Put this into a Directory.Build.props or Directory.Build.targets (as long as CMake doesn’t shut out ambient MSBuild extensions):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<!-- Enable build command logging -->
	<PropertyGroup>
		<ThisProjectBuildLogFileName Condition="'$(MSBuildProjectName)' == ''">$(MSBuildThisFileDirectory)BuildCommandLines.log</ThisProjectBuildLogFileName>
		<ThisProjectBuildLogFileName Condition="'$(MSBuildProjectName)' != ''">$(MSBuildThisFileDirectory)BuildCommandLines-$(MSBuildProjectName).log</ThisProjectBuildLogFileName>
	</PropertyGroup>
	<Target Name="LogBuildCommands" BeforeTargets="SetUserMacroEnvironmentVariables;SetBuildDefaultEnvironmentVariables" Condition="'$(DisableLogBuildCommands)' != 'true'">
		<Message Text="Setting LOG_BUILD_COMMANDLINES='$(ThisProjectBuildLogFileName)'" />
		<SetEnv Name="LOG_BUILD_COMMANDLINES" Value="$(ThisProjectBuildLogFileName)" Prefix="false" />
	</Target>
</Project>

This will create a log file for each of these projects. So you’ll get BuildCommandLines-CompilerIdC.log, BuildCommandLines-CompilerIdCXX.log and then plenty of log files with randomly generated names. I am counting 138 such files in my build, which is plenty much on a system where process creation is way more costly than on Unix.

The LOG_BUILD_COMMANDLINES trick still doesn’t seem to be widely known, so perhaps this will help spread the word.

@exoosh the generators map CMake’s code model to the native build system. They are not meant to provide low-level control over the generated build files. Nor are the generated build files meant to be relocatable or standalone in any way. If you’re trying to do any of those things then you’re going to be frustrated because CMake isn’t meant for such purposes.

@benthevining bracket arguments quote literal content for passing to a CMake language command. Once the command receives them there is nothing special about what it does with them.

As for the use case raised here, we don’t currently provide a way to express /pdbaltpath:%_PDB% within CMake’s code model. See CMake Issue 15865.

@brad.king Hi, I am trying no such thing. I am aware of CMake’s limitations and that’s why I am not voluntarily “in team CMake”. But there are those times when one has to bite the bullet because someone else decided CMake’s the best thing since sliced bread and you are left with their legacy :wink: (usually it’s not even that simple).

Still, don’t you think that — given the nature of these CMake tests being run silently — it might make sense for CMake to play nice with the MSBuild extension mechanisms at the disposal of the users?

To me it’s just another item on the long list of CMake idiosyncrasies known to me. But wouldn’t it make sense for CMake to shut out the most common MSBuild extension mechanisms so ambient configuration doesn’t silently fail the generator step?! I don’t find:

No CMAKE_CXX_COMPILER could be found

to be very much to the point, given the underlying cause. I.e. with ImportDirectoryBuildProps=false and ImportDirectoryBuildTargets=false.

Please open an issue to describe that problem and discuss possible solutions.