tsee / extutils-cppguess

Guess the C++ compiler for Perl modules
6 stars 9 forks source link

-lstdc++ flag ignored on static build because it occurs earlier on the link line than the object which needs it #27

Open djerius opened 2 years ago

djerius commented 2 years ago

ExtUtils::CppGuess version 0.26 Perl 5.34 Debian 11

I have a simple XS wrapper around : an Alien module

The wrapper uses Dist::Zilla with the following XS related stanzas:

[PPPort]

[MakeMaker::Awesome]
header = use Alien::Base::Wrapper qw( CXC::Alien::XTime !export );
header = use ExtUtils::CppGuess;
WriteMakefile_arg = Alien::Base::Wrapper->mm_args
WriteMakefile_arg = ExtUtils::CppGuess->new->makemaker_options

The problem arises in linking against the static Alien library. The link line looks fine:

cc  -L/home/dj/Work/Chandra-Time/local/lib/perl5/x86_64-linux/auto/share/dist/CXC-Alien-XTime/lib -shared -O2 -L/usr/local/lib -fstack-protector-strong  Time.o -lstdc++ -o blib/arch/auto/Chandra/Time/Time.so  \
   -L/home/dj/Work/Chandra-Time/local/lib/perl5/x86_64-linux/auto/share/dist/CXC-Alien-XTime/lib -lXTime   \

but running the Perl test code results in:

Can't load '/home/dj/Work/Chandra-Time/Chandra-Time-0.10/blib/arch/auto/Chandra/Time/Time.so' for module Chandra::Time: /home/dj/Work/Chandra-Time/Chandra-Time-0.10/blib/arch/auto/Chandra/Time/Time.so: undefined symbol: _ZTVN10__cxxabiv120__si_class_type_infoE at /home/dj/.plenv/versions/5.34/lib/perl5/5.34.1/x86_64-linux/DynaLoader.pm line 193.

Manually relinking with the Wl,--no-undefined

cc  -Wl,--no-undefined -L/home/dj/Work/Chandra-Time/local/lib/perl5/x86_64-linux/auto/share/dist/CXC-Alien-XTime/lib -shared -O2 -L/usr/local/lib -fstack-protector-strong  Time.o -lstdc++ -o blib/arch/auto/Chandra/Time/Time.so     -L/home/dj/Work/Chandra-Time/local/lib/perl5/x86_64-linux/auto/share/dist/CXC-Alien-XTime/lib -lXTime

provides some insight, spewing forth lots of undefined references to C++ std library symbols. But, there's definitely a -lstdc++ on the link line, so what gives? Unfortunately, it's before the link against the libXTime library, so it isn't used to resolve any symbols in objects which appear after it on the command line.

From TF (ld) M,

Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference.

Moving the -lstdc++ to the end of the link line solves all of the problems. Unfortunately, the hook that ExtUtils::CppGuess uses to insert -lstdc++ doesn't provide that flexibility.

I tried using ld's --start-group / --end-group options, but that works only if all of the libraries are in the same group, e.g.

-Wl,--start-group -lstdc++ -lXTime -Wl,--end-group

which isn't possible. using the OTHERLDFLAGS hook that ExtUtils::CppGuess uses.

Any ideas on how to approach this?

Thanks! Diab

djerius commented 2 years ago

This is a horrible hack, but it works. Add this line to the above MakeMaker::Awesome stanza in dist.ini:

WriteMakefile_arg = LIBS => [ map { $_ .= ' -lstdc++' } {Alien::Base::Wrapper->mm_args}->{LIBS}->@* ]
mohawk2 commented 2 years ago

If you hardcode -lstdc++ then it won't work on platforms where that isn't the correct thing. It looks like what you'd need to do in your override is to append the EUMM $(OTHERLDFLAGS) variable (EDIT i.e. literally that text in '') instead, which is from information supplied by this module. I will be pleased to accept a PR to update the docs.

djerius commented 2 years ago

It looks like what you'd need to do in your override is to append the EUMM $(OTHERLDFLAGS)

Darn. I was hoping for magic here.

Works, and with a bit of abstraction it's somewhat parseable:

header = use Alien::Base::Wrapper qw( CXC::Alien::XTime !export );
header = use ExtUtils::CppGuess;
header = use constant CPP_OTHERLDFLAGS => {ExtUtils::CppGuess->new->makemaker_options}->{dynamic_lib}{OTHERLDFLAGS};
header = use constant ALIEN_LIBS => {Alien::Base::Wrapper->mm_args}->{LIBS}->@*;
WriteMakefile_arg = Alien::Base::Wrapper->mm_args
WriteMakefile_arg = ExtUtils::CppGuess->new->makemaker_options
WriteMakefile_arg = LIBS => [ map { join ' ', $_, CPP_OTHERLDFLAGS } ALIEN_LIBS ]

Is {ExtUtils::CppGuess->new->makemaker_options}->{dynamic_lib}{OTHERLDFLAGS} guaranteed to exist, or do I need to handle undefined parts of the path?

I will be pleased to accept a PR to update the docs.

Will do. EDIT: will of course remove the duplicate call to ExtUtils::CppGuess->new

djerius commented 2 years ago

. It looks like what you'd need to do in your override is to append the EUMM $(OTHERLDFLAGS)

I peeked at the code, and it looks like OTHERLDFLAGS is just $self->_get_lflags, which is exposed via the public API as $self->linker_flags.

If I can assume that OTHERLDFLAGS == $self->linker_flags, then

use constant CPP_OTHERLDFLAGS => ExtUtils::CppGuess->new->linker_flags;

is a lot more palatable.

mohawk2 commented 2 years ago

That does look sensible. Probably you'd want use constant CPP_OTHERLDFLAGS => (my $cppg = ExtUtils::CppGuess->new)->linker_flags for deduplicating.

djerius commented 2 years ago

That does look sensible. Probably you'd want use constant CPP_OTHERLDFLAGS => (my $cppg = ExtUtils::CppGuess->new)->linker_flags for deduplicating.

Constants all the way down! Maximum use of CAPS!

use constant CPPG => ExtUtils::CppGuess->new;
use constant CPP_OTHERLDFLAGS => CPPG->linker_flags;
djerius commented 2 years ago

Constants all the way down! Maximum use of CAPS!

Errh. Too much late-day ☕.