Repository: mesonbuild/meson

Issue:
does not compile .vala file from configure_file()
if you add `configure_file(input: 'config.vala.in', output: 'config.vala', configuration: conf)` to sources, config.vala is not compiled and we get a ninja error `cc: error: app@exe/config.c: No such file or directory`



Discussion:
Did you add the result of `configure_file` to the sources for `app`?

yep

I can't reproduce this. Could you put your project up somewhere? Or perhaps a reduced version that still reproduces this.

Hmm, it seems to only break with subdirectories. Here is a test case: 
[bug.zip](https://github.com/mesonbuild/meson/files/491757/bug.zip)

The error is happening again. [bug.zip above](https://github.com/mesonbuild/meson/issues/815#issuecomment-249431005) doesn't build
@benwaffle I just stumbled on this error using version **0.44.0** on Arch Linux. That same build worked just fine with version **0.40.1** on Ubuntu 16.04.

I was luckily able to fix (or work around) the error by [moving everything to do with source compilation over to the /src subdirectory](https://github.com/ztefn/haguichi/commit/24d17006bf564a4f29a184d219264c41505f48e0).
Probably regressed because of #2413 That added --basedir to the valac flags so the output files follow the directory structure relative to the given base directory in the source. Could try reverting 1e3d03c as a starting point?

This only happens when the build directory is a subdir of the source tree. Annoying.

Resolving patch:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -14,6 +14,7 @@
 
 import os, pickle, re, shlex, subprocess, sys
 from collections import OrderedDict
+from pathlib import PurePath
 
 from . import backends
 from .. import modules
@@ -1141,16 +1142,31 @@ def generate_vala_compile(self, target, outfile):
         valac_outputs = []
         # All sources that are passed to valac on the commandline
         all_files = list(vapi_src.keys())
+        # Passed as --basedir
+        srcbasedir = os.path.join(self.build_to_src, target.get_subdir())
         for (vala_file, gensrc) in vala_src.items():
             all_files.append(vala_file)
             # Figure out where the Vala compiler will write the compiled C file
+            #
             # If the Vala file is in a subdir of the build dir (in our case
-            # because it was generated/built by something else), the subdir path
-            # components will be preserved in the output path. But if the Vala
-            # file is outside the build directory, the path components will be
-            # stripped and just the basename will be used.
+            # because it was generated/built by something else), and is also
+            # a subdir of --basedir (because the builddir is in the source
+            # tree, and the target subdir is the source root), the subdir
+            # components from the source root till the private builddir will be
+            # duplicated inside the private builddir. Otherwise, just the
+            # basename will be used.
+            #
+            # If the Vala file is outside the build directory, the paths from
+            # the --basedir till the subdir will be duplicated inside the
+            # private builddir.
             if isinstance(gensrc, (build.CustomTarget, build.GeneratedList)) or gensrc.is_built:
                 vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c'
+                # Check if the vala file is in a subdir of --basedir
+                abs_srcbasedir = os.path.join(self.environment.get_source_dir(), target.get_subdir())
+                abs_vala_file = os.path.join(self.environment.get_build_dir(), vala_file)
+                if PurePath(os.path.commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir):
+                    vala_c_subdir = PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir)
+                    vala_c_file = os.path.join(vala_c_subdir, vala_c_file)
             else:
                 path_to_target = os.path.join(self.build_to_src, target.get_subdir())
                 if vala_file.startswith(path_to_target):
@@ -1168,7 +1184,7 @@ def generate_vala_compile(self, target, outfile):
         # means it will also preserve the directory components of Vala sources
         # found inside the build tree (generated sources).
         args += ['--directory', c_out_dir]
-        args += ['--basedir', os.path.join(self.build_to_src, target.get_subdir())]
+        args += ['--basedir', srcbasedir]
         if not isinstance(target, build.Executable):
             # Library name
             args += ['--library', target.name]
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -897,6 +897,8 @@ def windows_proof_rmtree(f):
         try:
             shutil.rmtree(f)
             return
+        except FileNotFoundError:
+            return
         except (OSError, PermissionError):
             time.sleep(d)
     # Try one last time and throw if it fails.
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -464,10 +464,8 @@ def setUp(self):
         self.builddirs = []
         self.new_builddir()
 
-    def new_builddir(self):
-        # In case the directory is inside a symlinked directory, find the real
-        # path otherwise we might not find the srcdir from inside the builddir.
-        self.builddir = os.path.realpath(tempfile.mkdtemp())
+    def change_builddir(self, newdir):
+        self.builddir = newdir
         self.privatedir = os.path.join(self.builddir, 'meson-private')
         self.logdir = os.path.join(self.builddir, 'meson-logs')
         self.installdir = os.path.join(self.builddir, 'install')
@@ -475,6 +473,12 @@ def new_builddir(self):
         self.mtest_command = meson_command + ['test', '-C', self.builddir]
         self.builddirs.append(self.builddir)
 
+    def new_builddir(self):
+        # In case the directory is inside a symlinked directory, find the real
+        # path otherwise we might not find the srcdir from inside the builddir.
+        newdir = os.path.realpath(tempfile.mkdtemp())
+        self.change_builddir(newdir)
+
     def _print_meson_log(self):
         log = os.path.join(self.logdir, 'meson-log.txt')
         if not os.path.isfile(log):
@@ -2578,6 +2582,22 @@ def test_cross_file_system_paths(self):
                 self.init(testdir, ['--cross-file=' + name], inprocess=True)
                 self.wipe()
 
+    def test_vala_generated_source_buildir_inside_source_tree(self):
+        '''
+        Test that valac outputs generated C files in the expected location when
+        the builddir is a subdir of the source tree.
+        '''
+        testdir = os.path.join(self.vala_test_dir, '8 generated sources')
+        newdir = os.path.join(self.builddir, 'srctree')
+        shutil.copytree(testdir, newdir)
+        testdir = newdir
+        # New builddir
+        builddir = os.path.join(testdir, 'subdir/_build')
+        os.makedirs(builddir, exist_ok=True)
+        self.change_builddir(builddir)
+        self.init(testdir)
+        self.build()
+
 
 class LinuxArmCrossCompileTests(BasePlatformTests):
     '''


Test patch:
diff --git a/test cases/vala/8 generated sources/meson.build b/test cases/vala/8 generated sources/meson.build
--- a/test cases/vala/8 generated sources/meson.build	
+++ b/test cases/vala/8 generated sources/meson.build	
@@ -4,6 +4,11 @@ cd = configuration_data()
 cd.set('x', 'y')
 
 subdir('src')
+
+executable('generatedtestparent', [src, config, returncode, wrapper],
+  install : true,
+  dependencies: [dependency('glib-2.0'), dependency('gobject-2.0')])
+
 subdir('tools')
 subdir('onlygen')
 subdir('dependency-generated')