postgres/src/tools/msvc/Project.pm
Andres Freund 089480c077 Default to hidden visibility for extension libraries where possible
Until now postgres built extension libraries with global visibility, i.e.
exporting all symbols.  On the one platform where that behavior is not
natively available, namely windows, we emulate it by analyzing the input files
to the shared library and exporting all the symbols therein.

Not exporting all symbols is actually desirable, as it can improve loading
speed, reduces the likelihood of symbol conflicts and can improve intra
extension library function call performance. It also makes the non-windows
builds more similar to windows builds.

Additionally, with meson implementing the export-all-symbols behavior for
windows, turns out to be more verbose than desirable.

This patch adds support for hiding symbols by default and, to counteract that,
explicit symbol visibility annotation for compilers that support
__attribute__((visibility("default"))) and -fvisibility=hidden. That is
expected to be most, if not all, compilers except msvc (for which we already
support explicit symbol export annotations).

Now that extension library symbols are explicitly exported, we don't need to
export all symbols on windows anymore, hence remove that behavior from
src/tools/msvc. The supporting code can't be removed, as we still need to
export all symbols from the main postgres binary.

Author: Andres Freund <andres@anarazel.de>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/20211101020311.av6hphdl6xbjbuif@alap3.anarazel.de
2022-07-17 17:49:51 -07:00

488 lines
9.4 KiB
Perl

# Copyright (c) 2021-2022, PostgreSQL Global Development Group
package Project;
#
# Package that encapsulates a Visual C++ project file generation
#
# src/tools/msvc/Project.pm
#
use Carp;
use strict;
use warnings;
use File::Basename;
sub _new
{
my ($classname, $name, $type, $solution) = @_;
my $good_types = {
lib => 1,
exe => 1,
dll => 1,
};
confess("Bad project type: $type\n") unless exists $good_types->{$type};
my $self = {
name => $name,
type => $type,
guid => $^O eq "MSWin32" ? Win32::GuidGen() : 'FAKE',
files => {},
references => [],
libraries => [],
suffixlib => [],
includes => [],
prefixincludes => '',
defines => ';',
solution => $solution,
disablewarnings => '4018;4244;4273;4101;4102;4090;4267',
disablelinkerwarnings => '',
platform => $solution->{platform},
};
bless($self, $classname);
return $self;
}
sub AddFile
{
my ($self, $filename) = @_;
$self->FindAndAddAdditionalFiles($filename);
$self->{files}->{$filename} = 1;
return;
}
sub AddDependantFiles
{
my ($self, $filename) = @_;
$self->FindAndAddAdditionalFiles($filename);
return;
}
sub AddFiles
{
my $self = shift;
my $dir = shift;
while (my $f = shift)
{
$self->AddFile($dir . "/" . $f, 1);
}
return;
}
# Handle Makefile rules by searching for other files which exist with the same
# name but a different file extension and add those files too.
sub FindAndAddAdditionalFiles
{
my $self = shift;
my $fname = shift;
$fname =~ /(.*)(\.[^.]+)$/;
my $filenoext = $1;
my $fileext = $2;
# For .c files, check if either a .l or .y file of the same name
# exists and add that too.
if ($fileext eq ".c")
{
my $file = $filenoext . ".l";
if (-e $file)
{
$self->AddFile($file);
}
$file = $filenoext . ".y";
if (-e $file)
{
$self->AddFile($file);
}
}
}
sub ReplaceFile
{
my ($self, $filename, $newname) = @_;
my $re = "\\/$filename\$";
foreach my $file (keys %{ $self->{files} })
{
# Match complete filename
if ($filename =~ m!/!)
{
if ($file eq $filename)
{
delete $self->{files}{$file};
$self->AddFile($newname);
return;
}
}
elsif ($file =~ m/($re)/)
{
delete $self->{files}{$file};
$self->AddFile("$newname/$filename");
return;
}
}
confess("Could not find file $filename to replace\n");
}
sub RemoveFile
{
my ($self, $filename) = @_;
my $orig = scalar keys %{ $self->{files} };
delete $self->{files}->{$filename};
if ($orig > scalar keys %{ $self->{files} })
{
return;
}
confess("Could not find file $filename to remove\n");
}
sub RelocateFiles
{
my ($self, $targetdir, $proc) = @_;
foreach my $f (keys %{ $self->{files} })
{
my $r = &$proc($f);
if ($r)
{
$self->RemoveFile($f);
$self->AddFile($targetdir . '/' . basename($f));
}
}
return;
}
sub AddReference
{
my $self = shift;
while (my $ref = shift)
{
if (!grep { $_ eq $ref } @{ $self->{references} })
{
push @{ $self->{references} }, $ref;
}
$self->AddLibrary(
"__CFGNAME__/" . $ref->{name} . "/" . $ref->{name} . ".lib");
}
return;
}
sub AddLibrary
{
my ($self, $lib, $dbgsuffix) = @_;
# quote lib name if it has spaces and isn't already quoted
if ($lib =~ m/\s/ && $lib !~ m/^[&]quot;/)
{
$lib = '&quot;' . $lib . "&quot;";
}
if (!grep { $_ eq $lib } @{ $self->{libraries} })
{
push @{ $self->{libraries} }, $lib;
}
if ($dbgsuffix)
{
push @{ $self->{suffixlib} }, $lib;
}
return;
}
sub AddIncludeDir
{
my ($self, $incstr) = @_;
foreach my $inc (split(/;/, $incstr))
{
if (!grep { $_ eq $inc } @{ $self->{includes} })
{
push @{ $self->{includes} }, $inc;
}
}
return;
}
sub AddPrefixInclude
{
my ($self, $inc) = @_;
$self->{prefixincludes} = $inc . ';' . $self->{prefixincludes};
return;
}
sub AddDefine
{
my ($self, $def) = @_;
$def =~ s/"/&quot;&quot;/g;
$self->{defines} .= $def . ';';
return;
}
sub FullExportDLL
{
my ($self, $libname) = @_;
$self->{builddef} = 1;
$self->{def} = "./__CFGNAME__/$self->{name}/$self->{name}.def";
$self->{implib} = "__CFGNAME__/$self->{name}/$libname";
return;
}
sub UseDef
{
my ($self, $def) = @_;
$self->{def} = $def;
return;
}
sub AddDir
{
my ($self, $reldir) = @_;
my $mf = read_makefile($reldir);
$mf =~ s{\\\r?\n}{}g;
if ($mf =~ m{^(?:SUB)?DIRS[^=]*=\s*(.*)$}mg)
{
foreach my $subdir (split /\s+/, $1)
{
next
if $subdir eq "\$(top_builddir)/src/timezone"
; #special case for non-standard include
next
if $reldir . "/" . $subdir eq "src/backend/port/darwin";
$self->AddDir($reldir . "/" . $subdir);
}
}
while ($mf =~ m{^(?:EXTRA_)?OBJS[^=]*=\s*(.*)$}m)
{
my $s = $1;
my $filter_re = qr{\$\(filter ([^,]+),\s+\$\(([^\)]+)\)\)};
while ($s =~ /$filter_re/)
{
# Process $(filter a b c, $(VAR)) expressions
my $list = $1;
my $filter = $2;
$list =~ s/\.o/\.c/g;
my @pieces = split /\s+/, $list;
my $matches = "";
foreach my $p (@pieces)
{
if ($filter eq "LIBOBJS")
{
no warnings qw(once);
if (grep(/$p/, @main::pgportfiles) == 1)
{
$p =~ s/\.c/\.o/;
$matches .= $p . " ";
}
}
else
{
confess "Unknown filter $filter\n";
}
}
$s =~ s/$filter_re/$matches/;
}
foreach my $f (split /\s+/, $s)
{
next if $f =~ /^\s*$/;
next if $f eq "\\";
next if $f =~ /\/SUBSYS.o$/;
$f =~ s/,$//
; # Remove trailing comma that can show up from filter stuff
next unless $f =~ /.*\.o$/;
$f =~ s/\.o$/\.c/;
if ($f =~ /^\$\(top_builddir\)\/(.*)/)
{
$f = $1;
$self->AddFile($f);
}
else
{
$self->AddFile("$reldir/$f");
}
}
$mf =~ s{OBJS[^=]*=\s*(.*)$}{}m;
}
# Match rules that pull in source files from different directories, eg
# pgstrcasecmp.c rint.c snprintf.c: % : $(top_srcdir)/src/port/%
my $replace_re =
qr{^([^:\n\$]+\.c)\s*:\s*(?:%\s*: )?\$(\([^\)]+\))\/(.*)\/[^\/]+\n}m;
while ($mf =~ m{$replace_re}m)
{
my $match = $1;
my $top = $2;
my $target = $3;
my @pieces = split /\s+/, $match;
foreach my $fn (@pieces)
{
if ($top eq "(top_srcdir)")
{
eval { $self->ReplaceFile($fn, $target) };
}
elsif ($top eq "(backend_src)")
{
eval { $self->ReplaceFile($fn, "src/backend/$target") };
}
else
{
confess "Bad replacement top: $top, on line $_\n";
}
}
$mf =~ s{$replace_re}{}m;
}
$self->AddDirResourceFile($reldir);
return;
}
# If the directory's Makefile bears a description string, add a resource file.
sub AddDirResourceFile
{
my ($self, $reldir) = @_;
my $mf = read_makefile($reldir);
if ($mf =~ /^PGFILEDESC\s*=\s*\"([^\"]+)\"/m)
{
my $desc = $1;
my $ico;
if ($mf =~ /^PGAPPICON\s*=\s*(.*)$/m) { $ico = $1; }
$self->AddResourceFile($reldir, $desc, $ico);
}
return;
}
sub AddResourceFile
{
my ($self, $dir, $desc, $ico) = @_;
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
localtime(time);
my $d = sprintf("%02d%03d", ($year - 100), $yday);
if (Solution::IsNewer("$dir/win32ver.rc", 'src/port/win32ver.rc'))
{
print "Generating win32ver.rc for $dir\n";
open(my $i, '<', 'src/port/win32ver.rc')
|| confess "Could not open win32ver.rc";
open(my $o, '>', "$dir/win32ver.rc")
|| confess "Could not write win32ver.rc";
my $icostr = $ico ? "IDI_ICON ICON \"src/port/$ico.ico\"" : "";
while (<$i>)
{
s/FILEDESC/"$desc"/gm;
s/_ICO_/$icostr/gm;
s/(VERSION.*),0/$1,$d/;
if ($self->{type} eq "dll")
{
s/VFT_APP/VFT_DLL/gm;
my $name = $self->{name};
s/_INTERNAL_NAME_/"$name"/;
s/_ORIGINAL_NAME_/"$name.dll"/;
}
else
{
/_INTERNAL_NAME_/ && next;
/_ORIGINAL_NAME_/ && next;
}
print $o $_;
}
close($o);
close($i);
}
$self->AddFile("$dir/win32ver.rc");
return;
}
sub DisableLinkerWarnings
{
my ($self, $warnings) = @_;
$self->{disablelinkerwarnings} .= ','
unless ($self->{disablelinkerwarnings} eq '');
$self->{disablelinkerwarnings} .= $warnings;
return;
}
sub Save
{
my ($self) = @_;
# Warning 4197 is about double exporting, disable this per
# http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99193
$self->DisableLinkerWarnings('4197') if ($self->{platform} eq 'x64');
# Dump the project
open(my $f, '>', "$self->{name}$self->{filenameExtension}")
|| croak(
"Could not write to $self->{name}$self->{filenameExtension}\n");
$self->WriteHeader($f);
$self->WriteFiles($f);
$self->Footer($f);
close($f);
return;
}
sub GetAdditionalLinkerDependencies
{
my ($self, $cfgname, $separator) = @_;
my $libcfg = (uc $cfgname eq "RELEASE") ? "MD" : "MDd";
my $libs = '';
foreach my $lib (@{ $self->{libraries} })
{
my $xlib = $lib;
foreach my $slib (@{ $self->{suffixlib} })
{
if ($slib eq $lib)
{
$xlib =~ s/\.lib$/$libcfg.lib/;
last;
}
}
$libs .= $xlib . $separator;
}
$libs =~ s/.$//;
$libs =~ s/__CFGNAME__/$cfgname/g;
return $libs;
}
# Utility function that loads a complete file
sub read_file
{
my $filename = shift;
my $F;
local $/ = undef;
open($F, '<', $filename) || croak "Could not open file $filename\n";
my $txt = <$F>;
close($F);
return $txt;
}
sub read_makefile
{
my $reldir = shift;
my $F;
local $/ = undef;
open($F, '<', "$reldir/GNUmakefile")
|| open($F, '<', "$reldir/Makefile")
|| confess "Could not open $reldir/Makefile\n";
my $txt = <$F>;
close($F);
return $txt;
}
1;