Repository: mesonbuild/meson

Issue:
no include directories with precompiled headers ?
Hi,

i have a project structure tree that looks like:
lib/
&nbsp;&nbsp;&nbsp;&nbsp;inc
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lib
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lib.h
&nbsp;&nbsp;&nbsp;&nbsp;src
&nbsp;&nbsp;&nbsp;&nbsp;pch
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stdafx.h
app/
&nbsp;&nbsp;&nbsp;&nbsp;inc
&nbsp;&nbsp;&nbsp;&nbsp;src
&nbsp;&nbsp;&nbsp;&nbsp;pch
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stdafx.h

what i do (maybe it's a bad idead?), is that I include all the headers in my lib inside lib.h.
that way, any app using that needs lib can just include lib/lib.h.
i'm trying to use precompiled headers so that
lib/pch/stdafx.h contains lib.h
app/pch/stdafx.h also contains lib.h

meson.build for the app is setup like this:
executable( 'app'
            , src
            , include_directories : [lib_inc]
            , link_with : lib_lib
            , cpp_pch : ['pch/stdafx.h', 'pch/stdafx.cpp'])

problem is when i'm compiling, i have this error:
Precompiling header ../app/pch/stdafx.h
FAILED: app/app@exe/stdafx.h.pch 
clang++  "-Wall" "-Winvalid-pch" "-Wnon-virtual-dtor" "-std=c++11" "-DWINDOWS" "-DIX_DEBUG" "-O0" "-g" "-MMD" "-MQ" app/app@exe/stdafx.h.pch "-MF" "app\\app@exe\\stdafx.h.pch.d" -o app/app@exe/stdafx.h.pch -c ../app/pch/stdafx.h
clang++.exe: warning: treating 'c-header' input as 'c++-header' when in C++ mode, this behavior is deprecated
../app/pch/stdafx.h:5:10: fatal error: 'lib/lib.h' file not found
#include <lib/lib.h>

seems to me that the include_directories doesnt apply to precompiled headers. is it expected?
bonus: how can i get rid of the warning?
note that i'm running a meson.build script from the root directory, with subdir for lib and app.

thanks a lot in advance,
best regards,
Uj



Discussion:
The fact that includes are not used may be a bug. However it is strongly recommended that you don't include your own headers in pch, only those that come from "the system". If you change any header, it will trigger a recompilation of every source in your target, not just those that manually include just that one target.

thanks for your fast reply :-)

I know, but it's worth it considering I don't modify my libs so often.

Hi again,

i moved all "systems include" inside the stdafx and use my includes outside, but now i ran into another issue.
I have several libs and exes, like core (types and containers), display(openGL), sound, some apps, some tools..
How can i use more than 1 pch file?
as far as i understand pch have to be specified in each target, and we can't specify more than 2 pch files in the static_library() and executable() functions (in which i already specify the stdafx.h and stdafx.cpp for visual studio compliance)

for example, depending on the exes, some will need openGL, some dont.
Ideally i would use a separate pch for my core and display libs. Also note that the display lib depends on the core lib and would need to be aware of its pch.
Ideally pch files would be inherited when I link with a lib, so i dont have to specify it again per target.

what do you think?
thanks a lot in advance.

The problem with PCH is that every compiler does it differently. Trying to come up with a scheme that does inheritance and all that is very difficult (possibly impossible). This means that you have to manually write a pch file per each target. Fortunately this is not such a big issue because you don't need to change PCH files often and this allows you to tailor each target's PCH includes to match what they need.
Should we close this, because the recommendation is to write a separate PCH for each target?
The actual bug in this is that we don't set up dependency paths in pch compiles to be identical to ones in regular builds.

Resolving patch:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -428,12 +428,7 @@ def generate_target(self, target, outfile):
         # Generate rules for building the remaining source files in this target
         outname = self.get_target_filename(target)
         obj_list = []
-        use_pch = self.environment.coredata.base_options.get('b_pch', False)
         is_unity = self.is_unity(target)
-        if use_pch and target.has_pch():
-            pch_objects = self.generate_pch(target, outfile)
-        else:
-            pch_objects = []
         header_deps = []
         unity_src = []
         unity_deps = [] # Generated sources that must be built before compiling a Unity target.
@@ -486,6 +481,12 @@ def generate_target(self, target, outfile):
                                                  header_deps=header_deps)
             obj_list.append(o)
 
+        use_pch = self.environment.coredata.base_options.get('b_pch', False)
+        if use_pch and target.has_pch():
+            pch_objects = self.generate_pch(target, outfile, header_deps=header_deps)
+        else:
+            pch_objects = []
+
         # Generate compilation targets for C sources generated from Vala
         # sources. This can be extended to other $LANG->C compilers later if
         # necessary. This needs to be separate for at least Vala
@@ -2181,12 +2182,7 @@ def generate_single_compile(self, target, outfile, src, is_generated=False, head
             commands += compiler.get_module_outdir_args(self.get_target_private_dir(target))
 
         element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
-        for d in header_deps:
-            if isinstance(d, File):
-                d = d.rel_to_builddir(self.build_to_src)
-            elif not self.has_dir_part(d):
-                d = os.path.join(self.get_target_private_dir(target), d)
-            element.add_dep(d)
+        self.add_header_deps(target, element, header_deps)
         for d in extra_deps:
             element.add_dep(d)
         for d in order_deps:
@@ -2195,7 +2191,14 @@ def generate_single_compile(self, target, outfile, src, is_generated=False, head
             elif not self.has_dir_part(d):
                 d = os.path.join(self.get_target_private_dir(target), d)
             element.add_orderdep(d)
-        element.add_orderdep(pch_dep)
+        if compiler.id == 'msvc':
+            # MSVC does not show includes coming from the PCH with '/showIncludes',
+            # thus we must add an implicit dependency to the generated PCH.
+            element.add_dep(pch_dep)
+        else:
+            # All other compilers properly handle includes through the PCH, so only an
+            # orderdep is needed to make the initial build without depfile work.
+            element.add_orderdep(pch_dep)
         # Convert from GCC-style link argument naming to the naming used by the
         # current compiler.
         commands = commands.to_native()
@@ -2206,6 +2209,14 @@ def generate_single_compile(self, target, outfile, src, is_generated=False, head
         element.write(outfile)
         return rel_obj
 
+    def add_header_deps(self, target, ninja_element, header_deps):
+        for d in header_deps:
+            if isinstance(d, File):
+                d = d.rel_to_builddir(self.build_to_src)
+            elif not self.has_dir_part(d):
+                d = os.path.join(self.get_target_private_dir(target), d)
+            ninja_element.add_dep(d)
+
     def has_dir_part(self, fname):
         # FIXME FIXME: The usage of this is a terrible and unreliable hack
         if isinstance(fname, File):
@@ -2236,6 +2247,7 @@ def generate_msvc_pch_command(self, target, compiler, pch):
         just_name = os.path.basename(header)
         (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst)
         commands += pch_args
+        commands += self._generate_single_compile(target, compiler)
         commands += self.get_compile_debugfile_args(compiler, target, objname)
         dep = dst + '.' + compiler.get_depfile_suffix()
         return commands, dep, dst, [objname]
@@ -2251,7 +2263,7 @@ def generate_gcc_pch_command(self, target, compiler, pch):
         dep = dst + '.' + compiler.get_depfile_suffix()
         return commands, dep, dst, []  # Gcc does not create an object file during pch generation.
 
-    def generate_pch(self, target, outfile):
+    def generate_pch(self, target, outfile, header_deps=[]):
         cstr = ''
         pch_objects = []
         if target.is_cross:
@@ -2282,6 +2294,7 @@ def generate_pch(self, target, outfile):
             elem = NinjaBuildElement(self.all_outputs, dst, rulename, src)
             if extradep is not None:
                 elem.add_dep(extradep)
+            self.add_header_deps(target, elem, header_deps)
             elem.add_item('ARGS', commands)
             elem.add_item('DEPFILE', dep)
             elem.write(outfile)


Test patch:
diff --git a/test cases/common/13 pch/generated/gen_custom.py b/test cases/common/13 pch/generated/gen_custom.py
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/gen_custom.py	
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+import sys
+
+with open(sys.argv[1], 'w') as f:
+    f.write("#define FOO 0")
diff --git a/test cases/common/13 pch/generated/gen_generator.py b/test cases/common/13 pch/generated/gen_generator.py
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/gen_generator.py	
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+import sys
+
+with open(sys.argv[1]) as f:
+    content = f.read()
+with open(sys.argv[2], 'w') as f:
+    f.write(content)
diff --git a/test cases/common/13 pch/generated/generated_generator.in b/test cases/common/13 pch/generated/generated_generator.in
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/generated_generator.in	
@@ -0,0 +1 @@
+#define BAR 0
diff --git a/test cases/common/13 pch/generated/meson.build b/test cases/common/13 pch/generated/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/meson.build	
@@ -0,0 +1,16 @@
+cc = meson.get_compiler('c')
+cc_id = cc.get_id()
+if cc_id == 'lcc'
+  error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.')
+endif
+
+generated_customTarget = custom_target('makeheader',
+  output: 'generated_customTarget.h',
+  command : [find_program('gen_custom.py'), '@OUTPUT0@'])
+
+generated_generator = generator(find_program('gen_generator.py'),
+  output: '@BASENAME@.h',
+  arguments: ['@INPUT@', '@OUTPUT@'])
+
+exe = executable('prog', 'prog.c', generated_customTarget, generated_generator.process('generated_generator.in'),
+  c_pch: ['pch/prog_pch.c', 'pch/prog.h'])
diff --git a/test cases/common/13 pch/generated/pch/prog.h b/test cases/common/13 pch/generated/pch/prog.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/pch/prog.h	
@@ -0,0 +1,2 @@
+#include "generated_customTarget.h"
+#include "generated_generator.h"
diff --git a/test cases/common/13 pch/generated/pch/prog_pch.c b/test cases/common/13 pch/generated/pch/prog_pch.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/pch/prog_pch.c	
@@ -0,0 +1,5 @@
+#if !defined(_MSC_VER)
+#error "This file is only for use with MSVC."
+#endif
+
+#include "prog.h"
diff --git a/test cases/common/13 pch/generated/prog.c b/test cases/common/13 pch/generated/prog.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/generated/prog.c	
@@ -0,0 +1,6 @@
+// No includes here, they need to come from the PCH
+
+int main(int argc, char **argv) {
+    return FOO + BAR;
+}
+
diff --git a/test cases/common/13 pch/meson.build b/test cases/common/13 pch/meson.build
--- a/test cases/common/13 pch/meson.build	
+++ b/test cases/common/13 pch/meson.build	
@@ -2,6 +2,8 @@ project('pch test', 'c', 'cpp')
 
 subdir('c')
 subdir('cpp')
+subdir('generated')
+subdir('withIncludeDirectories')
 
 if meson.backend() == 'xcode'
   warning('Xcode backend only supports one precompiled header per target. Skipping "mixed" which has various precompiled headers.')
diff --git a/test cases/common/13 pch/withIncludeDirectories/include/lib/lib.h b/test cases/common/13 pch/withIncludeDirectories/include/lib/lib.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/withIncludeDirectories/include/lib/lib.h	
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/test cases/common/13 pch/withIncludeDirectories/meson.build b/test cases/common/13 pch/withIncludeDirectories/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/withIncludeDirectories/meson.build	
@@ -0,0 +1,9 @@
+cc = meson.get_compiler('c')
+cc_id = cc.get_id()
+if cc_id == 'lcc'
+  error('MESON_SKIP_TEST: Elbrus compiler does not support PCH.')
+endif
+
+exe = executable('prog', 'prog.c',
+  include_directories: 'include',
+  c_pch : ['pch/prog_pch.c', 'pch/prog.h'])
diff --git a/test cases/common/13 pch/withIncludeDirectories/pch/prog.h b/test cases/common/13 pch/withIncludeDirectories/pch/prog.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/withIncludeDirectories/pch/prog.h	
@@ -0,0 +1 @@
+#include<lib/lib.h>
diff --git a/test cases/common/13 pch/withIncludeDirectories/pch/prog_pch.c b/test cases/common/13 pch/withIncludeDirectories/pch/prog_pch.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/withIncludeDirectories/pch/prog_pch.c	
@@ -0,0 +1,5 @@
+#if !defined(_MSC_VER)
+#error "This file is only for use with MSVC."
+#endif
+
+#include "prog.h"
diff --git a/test cases/common/13 pch/withIncludeDirectories/prog.c b/test cases/common/13 pch/withIncludeDirectories/prog.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/13 pch/withIncludeDirectories/prog.c	
@@ -0,0 +1,10 @@
+// No includes here, they need to come from the PCH
+
+void func() {
+    fprintf(stdout, "This is a function that fails if stdio is not #included.\n");
+}
+
+int main(int argc, char **argv) {
+    return 0;
+}
+