Repository: mesonbuild/meson

Issue:
link_language has no effect
A patch from issue 1534 introduced link_language, but I believe that it is not working and the introduced test case does not test link_language, see https://github.com/mesonbuild/meson/issues/1534

The introduced patch does not seem to work and the supplied test case does not seem to be a proper test of the functionality. This is because the C component is only supplied as a library and it will therefore select the Fortran compiler for linking. From a discussion in #mesonbuild we figured out that "as far as executable() can tell, it is fortran-exclusive code, and shared libraries don't have a language type."

A minimal example is:

project('tproj', ['fortran','cpp'])
executable('run', ['test.f90','test.cpp'], link_language: 'fortran')

then it generates rule cpp_LINKER with the gcc command instead of gfortran


Discussion:
just to be clear, the cpp_LINKER rule *should* be generated with g++, however there should also be a fortran_LINKER rule with gfortran (or whatever), and your target should be using the fortran_LINKER, not the cpp_LINKER.

@scivision, you're probably interested
OK I thought it did work for some cases, but maybe I fooled myself or it was not really that general.
I believed it was taking effect for some projects, because I would get link failures without it. Specifically, where the "main" program was Fortran and I needed to link with `gfortran` instead of `gcc` that was autodetected by Meson without `link_language` parameter
I'm trying to do a similar thing forcing a C linker with some C++ sources using `both_libraries` but Meson is going a warning that `link_language` is not a valid keyword despite documentation states should be.
`link_language` is only valid for executables. It should only be necessary for executables because meson cannot always figure out which language implements the main() function.

What are you trying to achieve by using the C linker instead of the C++ linker?
To make sure I don't depend on C++ libraries. It's supported by autoconf so why you need to limit this feature? Shared libraries are not much different from executables, usually there's a flag/number in the binary to change it. From documentation (https://mesonbuild.com/Reference-manual.html)

```
Executable supports the following keyword arguments. Note that just like the positional arguments above, these keyword arguments can also be passed to shared and static libraries.
```
FWIW we like to have this in HarfBuzz also, we were able to choose our linker like this https://github.com/harfbuzz/harfbuzz/blob/748e1cf/src/Makefile.am#L120-L129 in autotools and implement the same in cmake https://github.com/harfbuzz/harfbuzz/blob/e860000/CMakeLists.txt#L481-L488 but not in meson apparently. We have a test against that in HarfBuzz https://github.com/harfbuzz/harfbuzz/blob/42040a2/src/check-libstdc++.sh and we like to enable that before switching to meson.

Resolving patch:
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -82,6 +82,7 @@
     'override_options',
     'sources',
     'gnu_symbol_visibility',
+    'link_language',
 ])
 
 known_build_target_kwargs = (
@@ -92,7 +93,7 @@
     rust_kwargs |
     cs_kwargs)
 
-known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'link_language', 'pie'}
+known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'}
 known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'}
 known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'}
 known_stlib_kwargs = known_build_target_kwargs | {'pic'}
@@ -1217,11 +1218,7 @@ def get_langs_used_by_deps(self) -> T.List[str]:
 
         See: https://github.com/mesonbuild/meson/issues/1653
         '''
-        langs = []
-
-        # User specified link_language of target (for multi-language targets)
-        if self.link_language:
-            return [self.link_language]
+        langs = [] # type: T.List[str]
 
         # Check if any of the external libraries were written in this language
         for dep in self.external_deps:
@@ -1253,6 +1250,12 @@ def get_clink_dynamic_linker_and_stdlibs(self):
         # Populate list of all compilers, not just those being used to compile
         # sources in this target
         all_compilers = self.environment.coredata.compilers[self.for_machine]
+
+        # If the user set the link_language, just return that.
+        if self.link_language:
+            comp = all_compilers[self.link_language]
+            return comp, comp.language_stdlib_only_link_flags()
+
         # Languages used by dependencies
         dep_langs = self.get_langs_used_by_deps()
         # Pick a compiler based on the language priority-order
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -4641,6 +4641,22 @@ def test_junit_valid_tap(self):
     def test_junit_valid_exitcode(self):
         self._test_junit('44 test args')
 
+    def test_link_language_linker(self):
+        # TODO: there should be some way to query how we're linking things
+        # without resorting to reading the ninja.build file
+        if self.backend is not Backend.ninja:
+            raise unittest.SkipTest('This test reads the ninja file')
+
+        testdir = os.path.join(self.common_test_dir, '232 link language')
+        self.init(testdir)
+
+        build_ninja = os.path.join(self.builddir, 'build.ninja')
+        with open(build_ninja, 'r', encoding='utf-8') as f:
+            contents = f.read()
+
+        self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER')
+        self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER')
+
 
 class FailureTests(BasePlatformTests):
     '''


Test patch:
diff --git a/test cases/common/232 link language/c_linkage.cpp b/test cases/common/232 link language/c_linkage.cpp
new file mode 100644
--- /dev/null
+++ b/test cases/common/232 link language/c_linkage.cpp	
@@ -0,0 +1,5 @@
+extern "C" {
+    int makeInt(void) {
+        return 0;
+    }
+}
diff --git a/test cases/common/232 link language/c_linkage.h b/test cases/common/232 link language/c_linkage.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/232 link language/c_linkage.h	
@@ -0,0 +1,10 @@
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int makeInt(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/test cases/common/232 link language/lib.cpp b/test cases/common/232 link language/lib.cpp
new file mode 100644
--- /dev/null
+++ b/test cases/common/232 link language/lib.cpp	
@@ -0,0 +1,5 @@
+extern "C" {
+    int makeInt(void) {
+        return 1;
+    }
+}
diff --git a/test cases/common/232 link language/main.c b/test cases/common/232 link language/main.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/232 link language/main.c	
@@ -0,0 +1,5 @@
+#include "c_linkage.h"
+
+int main(void) {
+    return makeInt();
+}
diff --git a/test cases/common/232 link language/meson.build b/test cases/common/232 link language/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/232 link language/meson.build	
@@ -0,0 +1,18 @@
+project(
+  'link_language',
+  ['c', 'cpp'],
+)
+
+exe = executable(
+  'main',
+  ['main.c', 'c_linkage.cpp'],
+  link_language : 'c',
+)
+
+lib = library(
+  'mylib',
+  ['lib.cpp'],
+  link_language : 'c',
+)
+
+test('main', exe)