I have a project that doesn't currently use precompiled headers, but I'd like to make it do so, as I've demonstrated that it leads to real compilation speed improvements in the project.
I'd also like to use /Zi
, so I can capitalize on the parallel build benefits associated with /Zf
, which is implied by /Zi
.
I'm using the VS2017 C++ compiler, but I'm using a build system that is not Visual Studio, so answers relating to configuring VS aren't helpful.
What I'm finding is that I can set up the build to use precompiled headers just fine, or I can set it up to use /Zi
just fine, but I can't seem to form a proper series of invocations to do both. When I try to do what I think is correct, I end up with error C2958
stopping the build, and I don't see what I'm doing wrong.
I've built a toy project to demonstrate what I'm seeing. The pch.hpp
header looks like this:
#pragma once
#include <vector>
And we make a short mainline in main.cpp
:
int main() {
std::vector<int> xs = { 1, 2, 3 };
return xs.size();
}
Note that this is the complete file contents for main.cpp
: I haven't omitted anything. I am intentionally not including pch.hpp
here, because we are going to force inject it with /Fi
later. The real project doesn't have include lines for a precompiled header in all the right places, and there would be thousands of files to update to do so. Note that the approach using /Fi
does appear to work in the command lines below, and has the advantage that a forceincludes based mechanism can work with GCC style precompiled headers as well.
If we build things without /Zi
, everything goes fine:
cl /Fobuild\pch.obj /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /c pch.hpp /Yc /Fpbuild\pch.pch
pch.hpp
cl /Fobuild\main.obj /c main.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Yupch.hpp /Fpbuild/pch.pch
main.cpp
We find the files we would expect to find under the build directory:
main.obj pch.obj pch.pch
However, if we try to use /Fi
and /Fd
to generate a per-file .pdb
and control its name, it doesn't work at all:
We can compile the precompiled header that way OK:
cl /Fobuild\pch.obj /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /c pch.hpp /Yc /Fpbuild\pch.pch /Zi /Fdbuild\pch.pch.pdb
And things look OK in the build
directory so far:
pch.obj pch.pch pch.pch.pdb
But when we try to build the object file for main, it all falls apart:
cl /Fobuild\main.obj /c main.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Yupch.hpp /Fpbuild/pch.pch /Zi /Fdbuild\main.obj.pdb
main.cpp
main.cpp: error C2859: Z:\data\acm\src\example\build\main.obj.pdb is not the pdb file that was used when this precompiled header was created, recreate the precompiled header.
This is a very puzzling error, because the error message suggests that main.obj.pdb
is being treated as an input somehow, but in fact it is intended to be the name of the .pdb
file generated as output, per the value of the /Fd
flag for the build of main.obj
.
Googling hasn't resulted in much useful guidance, with a lot of hints about reconfiguring VS settings, which isn't really useful in the case of a build driven by something else.
I've also verified that my trickery with the /Fi
of pch.hpp
isn't the issue. If I update main.cpp
to #include pch.hpp
and remove the /Fipch.hpp
from the compile line for main.obj
, the same C2859
error still occurs.
I realize there are a lot of flags to get right when using precompiled headers, among them the /Yc
and /Yu
and /Fp
flags, but I think I've got those handled correctly.
Does anyone see where I've got things wrong?
Edit 1 - Demonstrating that /Fd can name different PDB files
In response to the comment below, it doesn't actually appear to be the case that all targets must share the /Fd
argument. Here is an example without the use of precompiled headers that demonstrates that:
Here, I've needed to add common.cpp
and create a main1.cpp
and main2.cpp
which both link it:
The common.hpp
header looks like:
#pragma once
int common(int arg);
With a trivial implementation in common.cpp
:
#include "common.hpp"
int common(int arg) {
return arg + 42;
}
Then introduce two different mainlines:
main1.cpp
:
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3 };
return common(xs.size());
}
And main2.cpp
:
#include "common.hpp"
int main() {
std::vector<int> xs = { 1, 2, 3, 4};
return common(xs.size());
}
Then compile all three object files. Note that we still have the /Fipch.hpp
here to make the includes work right, but we aren't using any of the actual PCH machinery:
cl /Fobuild\common.obj /c common.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\common.obj.pdb
cl /Fobuild\main1.obj /c main1.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\main1.obj.pdb
cl /Fobuild\main2.obj /c main2.cpp /TP /nologo /EHsc /errorReport:none /MD /O2 /Oy- /Zc:rvalueCast /Zc:strictStrings /volatile:iso /Zc:__cplusplus /permissive- /std:c++17 /Zc:inline /FIpch.hpp /Zi /Fdbuild\main2.obj.pdb
And we find what we would expect in the build
directory:
common.obj common.obj.pdb main1.obj main1.obj.pdb main2.obj main2.obj.pdb
Then we can go ahead and link:
link /nologo /DEBUG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OUT:build\main1.exe /PDB:build\main1.pdb build\common.obj build\main1.obj
link /nologo /DEBUG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OUT:build\main2.exe /PDB:build\main2.pdb build\common.obj build\main2.obj
And we find that we get both executables and PDB files for those executables in the build
directory:
common.obj main1.exe main1.obj.pdb main2.exe main2.obj.pdb
common.obj.pdb main1.obj main1.pdb main2.obj main2.pdb