| # Copyright (C) all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| |
| # Just-ahead-of-time builder for the lib/PublicInbox/xap_helper.h shim. |
| # I never want users to be without source code for repairs, so this |
| # aims to replicate the feel of a scripting language using C++. |
| # The resulting executable is not linked to Perl in any way. |
| package PublicInbox::XapHelperCxx; |
| use v5.12; |
| use PublicInbox::Spawn qw(run_die run_qx run_wait which); |
| use PublicInbox::IO qw(try_cat write_file); |
| use PublicInbox::Search; |
| use Fcntl qw(SEEK_SET); |
| use Config; |
| use autodie; |
| my $cxx = which($ENV{CXX} // 'c++') // which('clang') // die 'no C++ compiler'; |
| my $dir = substr("$cxx-$Config{archname}", 1); # drop leading '/' |
| $dir =~ tr!/!-!; |
| my $idir; |
| if ((defined($ENV{XDG_CACHE_HOME}) && -d $ENV{XDG_CACHE_HOME}) || |
| (defined($ENV{HOME}) && -d $ENV{HOME})) { |
| $idir = ($ENV{XDG_CACHE_HOME} // |
| (($ENV{HOME} // die('HOME unset')).'/.cache') |
| ).'/public-inbox/jaot'; |
| } |
| $idir //= $ENV{PERL_INLINE_DIRECTORY} // |
| die 'HOME and PERL_INLINE_DIRECTORY unset'; |
| substr($dir, 0, 0) = "$idir/"; |
| my $bin = "$dir/xap_helper"; |
| my ($srcpfx) = (__FILE__ =~ m!\A(.+/)[^/]+\z!); |
| my @srcs = map { $srcpfx.$_ } qw(xh_mset.h xh_cidx.h xap_helper.h); |
| my @pm_dep = map { $srcpfx.$_ } qw(Search.pm CodeSearch.pm); |
| my $ldflags = '-Wl,-O1'; |
| $ldflags .= ' -Wl,--compress-debug-sections=zlib' if $^O ne 'openbsd'; |
| my $xflags = ($ENV{CXXFLAGS} // '-Wall -ggdb3 -pipe') . ' ' . |
| ' -DTHREADID=' . PublicInbox::Search::THREADID . |
| ' -DSHARD_COST=' . PublicInbox::Search::SHARD_COST . |
| ' -DXH_SPEC="'.join('', |
| map { s/=.*/:/; $_ } @PublicInbox::Search::XH_SPEC) . '" ' . |
| ($ENV{LDFLAGS} // $ldflags); |
| substr($xflags, 0, 0, '-O2 ') if !defined($ENV{CXXFLAGS}) && !-w __FILE__; |
| my $xap_modversion; |
| |
| sub xap_cfg (@) { |
| my $cmd = [ $ENV{PKG_CONFIG} // 'pkg-config', @_, 'xapian-core' ]; |
| chomp(my $ret = run_qx($cmd, undef, { 2 => \(my $err) })); |
| return $ret if !$?; |
| die <<EOM; |
| @$cmd failed: Xapian development files missing? (\$?=$?) |
| $err |
| EOM |
| } |
| |
| sub needs_rebuild () { |
| my $prev = try_cat("$dir/XFLAGS") or return 1; |
| chomp $prev; |
| return 1 if $prev ne $xflags; |
| |
| $prev = try_cat("$dir/xap_modversion") or return 1; |
| chomp $prev; |
| |
| $xap_modversion = xap_cfg('--modversion'); |
| $xap_modversion ne $prev; |
| } |
| |
| sub build () { |
| if (!-d $dir) { |
| require File::Path; |
| eval { File::Path::make_path($dir) }; |
| if (!-d $dir && defined($ENV{PERL_INLINE_DIRECTORY})) { |
| $dir = $ENV{PERL_INLINE_DIRECTORY}; |
| File::Path::make_path($dir); |
| } |
| } |
| require PublicInbox::CodeSearch; |
| require PublicInbox::Lock; |
| my ($prog) = ($bin =~ m!/([^/]+)\z!); |
| my $lk = PublicInbox::Lock->new("$dir/$prog.lock")->lock_for_scope; |
| write_file '>', "$dir/$prog.cpp", qq{#include "xap_helper.h"\n}, |
| PublicInbox::Search::generate_cxx(), |
| PublicInbox::CodeSearch::generate_cxx(); |
| |
| # xap_modversion may be set by needs_rebuild |
| $xap_modversion //= xap_cfg('--modversion'); |
| my $fl = xap_cfg(qw(--libs --cflags)); |
| |
| # Using rpath seems acceptable/encouraged in the NetBSD packaging world |
| # since /usr/pkg/lib isn't searched by the dynamic loader by default. |
| # Not sure if other OSes need this, but rpath seems fine for JAOT |
| # binaries (like this one) even if other distros discourage it for |
| # distributed packages. |
| $^O eq 'netbsd' and $fl =~ s/(\A|[ \t])\-L([^ \t]+)([ \t]|\z)/ |
| "$1-L$2 -Wl,-rpath=$2$3"/egsx; |
| my @xflags = split(' ', "$fl $xflags"); # ' ' awk-mode eats leading WS |
| my @cflags = ('-I', $srcpfx, grep(!/\A-(?:Wl|l|L)/, @xflags)); |
| run_die([$cxx, '-o', "$dir/$prog.o", '-c', "$dir/$prog.cpp", @cflags]); |
| |
| # xapian on Alpine Linux (tested 3.19.0) is linked against libstdc++, |
| # and clang needs to be told to use it (rather than libc++): |
| my @try = rindex($cxx, 'clang') >= 0 ? qw(-lstdc++) : (); |
| my @cmd = ($cxx, '-o', "$dir/$prog.tmp", "$dir/$prog.o", @xflags); |
| while (run_wait(\@cmd) and @try) { |
| warn("# attempting to link again with $try[0]...\n"); |
| push(@cmd, shift(@try)); |
| } |
| die "# @cmd failed: \$?=$?" if $?; |
| unlink "$dir/$prog.cpp", "$dir/$prog.o"; |
| write_file '>', "$dir/XFLAGS.tmp", $xflags, "\n"; |
| write_file '>', "$dir/xap_modversion.tmp", $xap_modversion, "\n"; |
| undef $xap_modversion; # do we ever build() twice? |
| # not quite atomic, but close enough :P |
| rename("$dir/$_.tmp", "$dir/$_") for ($prog, qw(XFLAGS xap_modversion)); |
| } |
| |
| sub check_build () { |
| use Time::HiRes qw(stat); |
| my $ctime = 0; |
| my @bin = stat($bin) or return build(); |
| for (@srcs, @pm_dep) { |
| my @st = stat($_) or die "stat $_: $!"; |
| if ($st[10] > $ctime) { |
| $ctime = $st[10]; |
| return build() if $ctime > $bin[10]; |
| } |
| } |
| needs_rebuild() ? build() : 0; |
| } |
| |
| # returns spawn arg |
| sub cmd { |
| die 'PI_NO_CXX set' if $ENV{PI_NO_CXX}; |
| check_build(); |
| my @cmd; |
| if (my $v = $ENV{VALGRIND}) { |
| $v = 'valgrind -v' if $v eq '1'; |
| @cmd = split(/\s+/, $v); |
| } |
| push @cmd, $bin; |
| \@cmd; |
| } |
| |
| 1; |