Repository: mesonbuild/meson

Issue:
Empty rpath-link path prevents linking libstdc++ with clang++ under Linux
Hi,

My meson build fails to link with clang++ on Linux because clang++ doesn't link in libstdc++.  This works with autotools, and it works with meson and gcc.  The reason seems to be that (for both g++ and clang++) the link command ends with `-Wl,-rpath-link,`.  This doesn't bother gcc, but it does bother clang++, probably because `-Wl,-rpath-link,` absorbs the next argument to the linker, which is probably `-lstdc++` or similar.

If I remove this or change it to  `-Wl,-rpath-link,yo` then it works.  Also, if I change
```
            args += ['-Wl,-rpath-link,' + lpaths]
```
to
```
            if lpaths != '':
                args += ['-Wl,-rpath-link,' + lpaths]
```
in compilers/compilers.py  Does this sound like the right fix?  Maybe the test should be for something containing only whitespace.

-BenRI


Discussion:
For reference, the complete link command is:
```
clang++  -o src/bali-phy 'src/bali-phy@exe/parser_parse.cc.o' 'src/bali-phy@exe/dp_dp_hmm.cc.o' 'src/bali-phy@exe/parser_desugar.cc.o' 'src/bali-phy@exe/substitution_substitution.cc.o' 'src/bali-phy@exe/util_ptree.cc.o' 'src/bali-phy@exe/mcmc_moves.cc.o' 'src/bali-phy@exe/math_exponential.cc.o' 'src/bali-phy@exe/math_eigenvalue.cc.o' 'src/bali-phy@exe/models_parameters.cc.o' 'src/bali-phy@exe/prior.cc.o' 'src/bali-phy@exe/mcmc_mcmc.cc.o' 'src/bali-phy@exe/probability_choose.cc.o' 'src/bali-phy@exe/mcmc_sample-branch-lengths.cc.o' 'src/bali-phy@exe/bali-phy.cc.o' 'src/bali-phy@exe/models_TreeInterface.cc.o' 'src/bali-phy@exe/dp_hmm.cc.o' 'src/bali-phy@exe/dp_dp-engine.cc.o' 'src/bali-phy@exe/dp_dp-array.cc.o' 'src/bali-phy@exe/dp_dp-matrix.cc.o' 'src/bali-phy@exe/dp_3way.cc.o' 'src/bali-phy@exe/dp_2way.cc.o' 'src/bali-phy@exe/mcmc_sample-alignment.cc.o' 'src/bali-phy@exe/mcmc_sample-node.cc.o' 'src/bali-phy@exe/imodel_imodel.cc.o' 'src/bali-phy@exe/dp_5way.cc.o' 'src/bali-phy@exe/mcmc_sample-topology-NNI.cc.o' 'src/bali-phy@exe/mcmc_sample-two-nodes.cc.o' 'src/bali-phy@exe/models_setup.cc.o' 'src/bali-phy@exe/mcmc_sample-topology-SPR.cc.o' 'src/bali-phy@exe/dp_alignment-sums.cc.o' 'src/bali-phy@exe/probability_probability.cc.o' 'src/bali-phy@exe/models_model.cc.o' 'src/bali-phy@exe/alignment_alignment-constraint.cc.o' 'src/bali-phy@exe/monitor.cc.o' 'src/bali-phy@exe/myexception.cc.o' 'src/bali-phy@exe/math_pow2.cc.o' 'src/bali-phy@exe/substitution_parsimony.cc.o' 'src/bali-phy@exe/mcmc_proposals.cc.o' 'src/bali-phy@exe/n_indels2.cc.o' 'src/bali-phy@exe/alignment_alignment-util2.cc.o' 'src/bali-phy@exe/tools_parsimony2.cc.o' 'src/bali-phy@exe/version.cc.o' 'src/bali-phy@exe/mcmc_slice-sampling.cc.o' 'src/bali-phy@exe/timer_stack.cc.o' 'src/bali-phy@exe/mcmc_setup.cc.o' 'src/bali-phy@exe/mcmc_logger.cc.o' 'src/bali-phy@exe/mcmc_AIS.cc.o' 'src/bali-phy@exe/computation_operator.cc.o' 'src/bali-phy@exe/computation_expression_expression.cc.o' 'src/bali-phy@exe/computation_expression_constructor.cc.o' 'src/bali-phy@exe/computation_expression_expression_ref.cc.o' 'src/bali-phy@exe/computation_expression_AST_node.cc.o' 'src/bali-phy@exe/computation_expression_apply.cc.o' 'src/bali-phy@exe/computation_expression_substitute.cc.o' 'src/bali-phy@exe/computation_expression_indexify.cc.o' 'src/bali-phy@exe/computation_expression_let.cc.o' 'src/bali-phy@exe/computation_expression_case.cc.o' 'src/bali-phy@exe/computation_expression_trim.cc.o' 'src/bali-phy@exe/computation_expression_tuple.cc.o' 'src/bali-phy@exe/computation_expression_list.cc.o' 'src/bali-phy@exe/computation_expression_dummy.cc.o' 'src/bali-phy@exe/computation_expression_lambda.cc.o' 'src/bali-phy@exe/computation_computation.cc.o' 'src/bali-phy@exe/computation_machine_tokens.cc.o' 'src/bali-phy@exe/computation_preprocess.cc.o' 'src/bali-phy@exe/computation_machine_graph_register.cc.o' 'src/bali-phy@exe/computation_machine_show_graph.cc.o' 'src/bali-phy@exe/computation_module.cc.o' 'src/bali-phy@exe/computation_model_expression.cc.o' 'src/bali-phy@exe/computation_machine_evaluate.cc.o' 'src/bali-phy@exe/computation_machine_gc.cc.o' 'src/bali-phy@exe/computation_machine_reroot.cc.o' 'src/bali-phy@exe/computation_operations.cc.o' 'src/bali-phy@exe/computation_loader.cc.o' 'src/bali-phy@exe/computation_context.cc.o' 'src/bali-phy@exe/computation_closure.cc.o' 'src/bali-phy@exe/computation_optimization_let-float.cc.o' 'src/bali-phy@exe/computation_program.cc.o' 'src/bali-phy@exe/mcmc_sample-tri.cc.o' 'src/bali-phy@exe/startup_A-T-model.cc.o' 'src/bali-phy@exe/startup_files.cc.o' 'src/bali-phy@exe/startup_loggers.cc.o' 'src/bali-phy@exe/startup_system.cc.o' 'src/bali-phy@exe/startup_cmd_line.cc.o' 'src/bali-phy@exe/models_rules.cc.o' 'src/bali-phy@exe/models_parse.cc.o' 'src/bali-phy@exe/models_translate.cc.o' 'src/bali-phy@exe/models_unification.cc.o' 'src/bali-phy@exe/computation_optimization_simplifier.cc.o' 'src/bali-phy@exe/startup_paths.cc.o' 'src/bali-phy@exe/computation_optimization_occurrence.cc.o' 'src/bali-phy@exe/computation_optimization_inliner.cc.o' -Wl,--no-undefined -Wl,--as-needed -Wl,-O1 -Wl,--start-group src/libbali-phy.a boost/lib/program_options/libboost_program_options.a boost/lib/random/libboost_random_options.a boost/lib/system/libboost_system.a boost/lib/filesystem/libboost_filesystem.a boost/lib/chrono/libboost_chrono.a src/liblibsums.a -rdynamic -ldl -lrt -Wl,--end-group -lrt -lrt -Wl,-rpath,XXXXXXXXXXXXXXXXXXXXXXXX -Wl,-rpath-link,  
```
Seems like a reasonable fix to me. Would it be possible to create a simple test case for this?
Sure.  I will create a reduced test case by reducing the larger project.  I have not managed to reproduce it by writing anything simple.


My simple test case didn't have the error.  I inspected the arguments passed to the linker with `strace -v -s128 -o log -ff clang++ <args>`.  This shows that in the working case we end up the following, so the rpath-link command doesn't eat the "-lstdc++":
```
... "-rpath", "XXXXXXXXXXXXXXXXXXXXXXXX", "-rpath-link", "", "-lstdc++", "-lm", "-lgcc_s", "-lgcc", "-lc" ...
```
However, in the failing case, we end up with:
```
... "-rpath", "XXXXXXXXXXXXXXXXXXXXXXXX", "-rpath-link", "-lstdc++", "-lm", "-lgcc_s", "-lgcc", "-lc" ...
```
Here, clearly the -rpath-link command eats the `-lstdc++`.

The question is where the "" comes from and why.
OK, I got a reduced testcase:
[testcase.tar.gz](https://github.com/mesonbuild/meson/files/1584438/testcase.tar.gz)
It looks like you just have to set install_rpath and use clang to compile on linux.
``` sh
% cd testcase
% CC=clang CXX=clang++ meson build
% ninja -C build
% ninja -C build
ninja: Entering directory `build'
[2/2] Linking target testcase.
FAILED: testcase 
clang++  -o testcase 'testcase@exe/testcase.cc.o' -Wl,--no-undefined -Wl,--as-needed -Wl,-rpath,XXXXXXXXXXXXXXXXXXXXXXXX -Wl,-rpath-link,  
testcase@exe/testcase.cc.o: In function `main':
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `operator new(unsigned long)'
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `std::allocator<char>::allocator()'
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)'
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `std::allocator<char>::~allocator()'
/home/bredelings/testcase/build/../testcase.cc:7: undefined reference to `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()'
/home/bredelings/testcase/build/../testcase.cc:7: undefined reference to `operator delete(void*)'
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `std::allocator<char>::~allocator()'
/home/bredelings/testcase/build/../testcase.cc:6: undefined reference to `operator delete(void*)'
/home/bredelings/testcase/build/../testcase.cc:7: undefined reference to `operator delete(void*)'
testcase@exe/testcase.cc.o: In function `__cxx_global_var_init':
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/iostream:74: undefined reference to `std::ios_base::Init::Init()'
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../../include/c++/7.2.0/iostream:74: undefined reference to `std::ios_base::Init::~Init()'
testcase@exe/testcase.cc.o:(.eh_frame+0x47): undefined reference to `__gxx_personality_v0'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
```

Resolving patch:
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -863,7 +863,12 @@ def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, i
             # Not needed on Windows or other platforms that don't use RPATH
             # https://github.com/mesonbuild/meson/issues/1897
             lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
-            args += ['-Wl,-rpath-link,' + lpaths]
+
+            # clang expands '-Wl,rpath-link,' to ['-rpath-link'] instead of ['-rpath-link','']
+            # This eats the next argument, which happens to be 'ldstdc++', causing link failures.
+            # We can dodge this problem by not adding any rpath_paths if the argument is empty.
+            if lpaths.strip() != '':
+                args += ['-Wl,-rpath-link,' + lpaths]
         return args
 
 
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -2288,11 +2288,18 @@ def test_build_rpath(self):
         testdir = os.path.join(self.unit_test_dir, '11 build_rpath')
         self.init(testdir)
         self.build()
+        # C program RPATH
         build_rpath = get_rpath(os.path.join(self.builddir, 'prog'))
         self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
         self.install()
         install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog'))
         self.assertEqual(install_rpath, '/baz')
+        # C++ program RPATH
+        build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx'))
+        self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
+        self.install()
+        install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx'))
+        self.assertEqual(install_rpath, 'baz')
 
     def test_pch_with_address_sanitizer(self):
         testdir = os.path.join(self.common_test_dir, '13 pch')


Test patch:
diff --git a/test cases/unit/11 build_rpath/meson.build b/test cases/unit/11 build_rpath/meson.build
--- a/test cases/unit/11 build_rpath/meson.build	
+++ b/test cases/unit/11 build_rpath/meson.build	
@@ -1,4 +1,4 @@
-project('build rpath', 'c')
+project('build rpath', 'c', 'cpp')
 
 subdir('sub')
 executable('prog', 'prog.c',
@@ -7,3 +7,10 @@ executable('prog', 'prog.c',
   install_rpath : '/baz',
   install : true,
   )
+
+executable('progcxx', 'prog.cc',
+  link_with : l,
+  build_rpath : '/foo/bar',
+  install_rpath : 'baz',
+  install : true,
+  )
diff --git a/test cases/unit/11 build_rpath/prog.cc b/test cases/unit/11 build_rpath/prog.cc
new file mode 100644
--- /dev/null
+++ b/test cases/unit/11 build_rpath/prog.cc	
@@ -0,0 +1,8 @@
+#include <string>
+#include <iostream>
+
+int main(int argc, char **argv) {
+    std::string* s = new std::string("Hello");
+    delete s;
+    return 0;
+}