Repository: mesonbuild/meson

Issue:
link_depends does not work with files()
Hey

The `link_depends:` argument to executables and libraries does not work with `File` objects so far. It requires strings. See this example:

```
project('foobar', 'c')                                                           
bar = shared_library('bar', ['foo.c'], link_depends: files(['A','B']))
```

It fails with:

```
Link_depends arguments must be strings.
```

I circumvent this by using `join_paths(meson.current_source_dir(), 'my_file')` instead of `files()`, but I guess that's no the suggested solution?

Furthermore, the reference-manual in the wiki does not explain the `File` object, but the documentation of `files()` mentions it.

Thanks
David


Discussion:
Same for `install_headers()`. I guess there are a bunch more. Maybe `files()` is not supposed to used with those functions, but the wiki gives the impression that `files()` should work with anything that accepts paths. Or am I wrong here?
This is a bug. Generally, everything that accepts files as strings should also accept them as `File` objects. The reason that some things do not is because almost everyone uses strings and `File` objects were not there in the very beginning of the project when most of these functions were added.
It seems that [`generator.process()`](https://github.com/mesonbuild/meson/wiki/Reference%20manual#generator-object) also doesn't accept `file_array` (which is the output of `files()`.) I'm getting the following error:
```
Arguments must be strings.
```

@nirbheek, would you rather I open a new issue about it?

### Edit:
It actually seems it was mentioned in #1141.
That was merged a few days ago in https://github.com/mesonbuild/meson/pull/1257.
For other people reading this bug report, you almost never need to use `meson.current_source_dir()` when passing files as strings to functions. Strings are implicitly assume to be relative to the current source dir. The only time you want to use that is when you want to get the absolute path for some custom reasons.

Resolving patch:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -2304,8 +2304,8 @@ def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[
         # current compiler.
         commands = commands.to_native()
         dep_targets = [self.get_dependency_filename(t) for t in dependencies]
-        dep_targets += [os.path.join(self.environment.source_dir,
-                                     target.subdir, t) for t in target.link_depends]
+        dep_targets.extend([self.get_dependency_filename(t)
+                            for t in target.link_depends])
         elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list)
         elem.add_dep(dep_targets + custom_target_libraries)
         elem.add_item('LINK_ARGS', commands)
@@ -2323,6 +2323,12 @@ def determine_rpath_dirs(self, target):
     def get_dependency_filename(self, t):
         if isinstance(t, build.SharedLibrary):
             return os.path.join(self.get_target_private_dir(t), self.get_target_filename(t) + '.symbols')
+        elif isinstance(t, mesonlib.File):
+            if t.is_built:
+                return t.relative_name()
+            else:
+                return t.absolute_path(self.environment.get_source_dir(),
+                                       self.environment.get_build_dir())
         return self.get_target_filename(t)
 
     def generate_shlib_aliases(self, target, outdir):
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -503,6 +503,34 @@ def validate_sources(self):
                 assert(len(self.compilers) == 1)
                 return
 
+    def process_link_depends(self, sources, environment):
+        """Process the link_depends keyword argument.
+
+        This is designed to handle strings, Files, and the output of Custom
+        Targets. Notably it doesn't handle generator() returned objects, since
+        adding them as a link depends would inherently cause them to be
+        generated twice, since the output needs to be passed to the ld_args and
+        link_depends.
+        """
+        if not isinstance(sources, list):
+            sources = [sources]
+        for s in sources:
+            if hasattr(s, 'held_object'):
+                s = s.held_object
+
+            if isinstance(s, File):
+                self.link_depends.append(s)
+            elif isinstance(s, str):
+                self.link_depends.append(
+                    File.from_source_file(environment.source_dir, self.subdir, s))
+            elif hasattr(s, 'get_outputs'):
+                self.link_depends.extend(
+                    [File.from_built_file(s.subdir, p) for p in s.get_outputs()])
+            else:
+                raise InvalidArguments(
+                    'Link_depends arguments must be strings, Files, '
+                    'or a Custom Target, or lists thereof.')
+
     def get_original_kwargs(self):
         return self.kwargs
 
@@ -616,12 +644,7 @@ def process_kwargs(self, kwargs, environment):
         for i in self.link_args:
             if not isinstance(i, str):
                 raise InvalidArguments('Link_args arguments must be strings.')
-        self.link_depends = kwargs.get('link_depends', [])
-        if not isinstance(self.link_depends, list):
-            self.link_depends = [self.link_depends]
-        for i in self.link_depends:
-            if not isinstance(i, str):
-                raise InvalidArguments('Link_depends arguments must be strings.')
+        self.process_link_depends(kwargs.get('link_depends', []), environment)
         # Target-specific include dirs must be added BEFORE include dirs from
         # internal deps (added inside self.add_deps()) to override them.
         inclist = kwargs.get('include_directories', [])
@@ -1257,13 +1280,11 @@ def process_kwargs(self, kwargs, environment):
                     self.vs_module_defs = File.from_absolute_file(path)
                 else:
                     self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path)
-                # link_depends can be an absolute path or relative to self.subdir
-                self.link_depends.append(path)
+                self.link_depends.append(self.vs_module_defs)
             elif isinstance(path, File):
                 # When passing a generated file.
                 self.vs_module_defs = path
-                # link_depends can be an absolute path or relative to self.subdir
-                self.link_depends.append(path.absolute_path(environment.source_dir, environment.build_dir))
+                self.link_depends.append(path)
             else:
                 raise InvalidArguments(
                     'Shared library vs_module_defs must be either a string, '


Test patch:
diff --git a/test cases/linuxlike/3 linker script/bob.map.in b/test cases/linuxlike/3 linker script/bob.map.in
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/3 linker script/bob.map.in	
@@ -0,0 +1,6 @@
+V1_0_0 {
+  global:
+    "@in@";
+  local:
+    *;
+};
diff --git a/test cases/linuxlike/3 linker script/copy.py b/test cases/linuxlike/3 linker script/copy.py
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/3 linker script/copy.py	
@@ -0,0 +1,5 @@
+import shutil
+import sys
+
+if __name__ == '__main__':
+    shutil.copy(sys.argv[1], sys.argv[2])
diff --git a/test cases/linuxlike/3 linker script/meson.build b/test cases/linuxlike/3 linker script/meson.build
--- a/test cases/linuxlike/3 linker script/meson.build	
+++ b/test cases/linuxlike/3 linker script/meson.build	
@@ -1,8 +1,56 @@
 project('linker script', 'c')
 
+# Static map file
 mapfile = 'bob.map'
 vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile)
 
 l = shared_library('bob', 'bob.c', link_args : vflag, link_depends : mapfile)
 e = executable('prog', 'prog.c', link_with : l)
 test('core', e)
+
+# configure_file
+conf = configuration_data()
+conf.set('in', 'bobMcBob')
+m = configure_file(
+  input : 'bob.map.in',
+  output : 'bob-conf.map',
+  configuration : conf,
+)
+vflag = '-Wl,--version-script,@0@'.format(m)
+
+l = shared_library('bob-conf', 'bob.c', link_args : vflag, link_depends : m)
+e = executable('prog-conf', 'prog.c', link_with : l)
+test('core', e)
+
+# custom_target
+python = find_program('python3')
+m = custom_target(
+  'bob-ct.map',
+  command : [python, '@INPUT0@', '@INPUT1@', 'bob-ct.map'],
+  input : ['copy.py', 'bob.map'],
+  output : 'bob-ct.map',
+  depend_files : 'bob.map',
+)
+vflag = '-Wl,--version-script,@0@'.format(m.full_path())
+
+l = shared_library('bob-ct', ['bob.c', m], link_args : vflag, link_depends : m)
+e = executable('prog-ct', 'prog.c', link_with : l)
+test('core', e)
+
+# File
+mapfile = files('bob.map')
+vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile[0])
+
+l = shared_library('bob-files', 'bob.c', link_args : vflag, link_depends : mapfile)
+e = executable('prog-files', 'prog.c', link_with : l)
+test('core', e)
+
+subdir('sub')
+
+# With map file in subdir
+mapfile = 'sub/foo.map'
+vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile)
+
+l = shared_library('bar', 'bob.c', link_args : vflag, link_depends : mapfile)
+e = executable('prog-bar', 'prog.c', link_with : l)
+test('core', e)
diff --git a/test cases/linuxlike/3 linker script/sub/foo.map b/test cases/linuxlike/3 linker script/sub/foo.map
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/3 linker script/sub/foo.map	
@@ -0,0 +1,6 @@
+V1_0_0 {
+  global:
+    "bobMcBob";
+  local:
+    *;
+};
diff --git a/test cases/linuxlike/3 linker script/sub/meson.build b/test cases/linuxlike/3 linker script/sub/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/3 linker script/sub/meson.build	
@@ -0,0 +1,6 @@
+mapfile = 'foo.map'
+vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile)
+
+l = shared_library('foo', '../bob.c', link_args : vflag, link_depends : mapfile)
+e = executable('prog-foo', '../prog.c', link_with : l)
+test('core', e)