diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/msvc/Install.pm | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm new file mode 100644 index 0000000..de22c9b --- /dev/null +++ b/src/tools/msvc/Install.pm @@ -0,0 +1,777 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Install; + +# +# Package that provides 'make install' functionality for msvc builds +# +# src/tools/msvc/Install.pm +# +use strict; +use warnings; +use Carp; +use File::Basename; +use File::Copy; +use File::Find (); + +use Exporter; +our (@ISA, @EXPORT_OK); +@ISA = qw(Exporter); +@EXPORT_OK = qw(Install); + +my $insttype; +my @client_contribs = ('oid2name', 'pgbench', 'vacuumlo'); +my @client_program_files = ( + 'clusterdb', 'createdb', 'createuser', 'dropdb', + 'dropuser', 'ecpg', 'libecpg', 'libecpg_compat', + 'libpgtypes', 'libpq', 'pg_amcheck', 'pg_basebackup', + 'pg_config', 'pg_dump', 'pg_dumpall', 'pg_isready', + 'pg_receivewal', 'pg_recvlogical', 'pg_restore', 'psql', + 'reindexdb', 'vacuumdb', @client_contribs); + +sub lcopy +{ + my $src = shift; + my $target = shift; + + if (-f $target) + { + unlink $target || confess "Could not delete $target\n"; + } + + (my $retval = copy($src, $target)) + || confess "Could not copy $src to $target\n"; + + return $retval; +} + +sub Install +{ + $| = 1; + + my $target = shift; + $insttype = shift; + $insttype = "all" unless ($insttype); + + # if called from vcregress, the config will be passed to us + # so no need to re-include these + our $config = shift; + unless ($config) + { + + # suppress warning about harmless redeclaration of $config + no warnings 'misc'; + do "./config_default.pl"; + do "./config.pl" if (-f "config.pl"); + } + + # Move to the root path depending on the current location. + if (-f "../../../configure") + { + chdir("../../.."); + } + elsif (-f "../../../../configure") + { + chdir("../../../.."); + } + + my $conf = ""; + if (-d "debug") + { + $conf = "debug"; + } + if (-d "release") + { + $conf = "release"; + } + die "Could not find debug or release binaries" if ($conf eq ""); + my $majorver = DetermineMajorVersion(); + print "Installing version $majorver for $conf in $target\n"; + + my @client_dirs = ('bin', 'lib', 'share', 'symbols'); + my @all_dirs = ( + @client_dirs, 'doc', 'doc/contrib', 'doc/extension', 'share/contrib', + 'share/extension', 'share/timezonesets', 'share/tsearch_data'); + if ($insttype eq "client") + { + EnsureDirectories($target, @client_dirs); + } + else + { + EnsureDirectories($target, @all_dirs); + } + + CopySolutionOutput($conf, $target); + my $sample_files = []; + my @top_dir = ("src"); + @top_dir = ("src\\bin", "src\\interfaces") if ($insttype eq "client"); + File::Find::find( + { + wanted => sub { + /^.*\.sample\z/s + && push(@$sample_files, $File::Find::name); + + # Don't find files of in-tree temporary installations. + $_ eq 'share' and $File::Find::prune = 1; + } + }, + @top_dir); + CopySetOfFiles('config files', $sample_files, $target . '/share/'); + CopyFiles( + 'Import libraries', + $target . '/lib/', + "$conf\\", "postgres\\postgres.lib", "libpgcommon\\libpgcommon.lib", + "libpgport\\libpgport.lib"); + CopyContribFiles($config, $target); + CopyIncludeFiles($target); + + if ($insttype ne "client") + { + CopySetOfFiles( + 'timezone names', + [ glob('src\timezone\tznames\*.txt') ], + $target . '/share/timezonesets/'); + CopyFiles( + 'timezone sets', + $target . '/share/timezonesets/', + 'src/timezone/tznames/', 'Default', 'Australia', 'India'); + CopySetOfFiles( + 'BKI files', + [ glob("src\\backend\\catalog\\postgres.*") ], + $target . '/share/'); + CopySetOfFiles( + 'SQL files', + [ glob("src\\backend\\catalog\\*.sql") ], + $target . '/share/'); + CopyFiles( + 'Information schema data', $target . '/share/', + 'src/backend/catalog/', 'sql_features.txt'); + CopyFiles( + 'Error code data', $target . '/share/', + 'src/backend/utils/', 'errcodes.txt'); + GenerateTimezoneFiles($target, $conf); + GenerateTsearchFiles($target); + CopySetOfFiles( + 'Stopword files', + [ glob("src\\backend\\snowball\\stopwords\\*.stop") ], + $target . '/share/tsearch_data/'); + CopySetOfFiles( + 'Dictionaries sample files', + [ glob("src\\backend\\tsearch\\dicts\\*_sample*") ], + $target . '/share/tsearch_data/'); + + my $pl_extension_files = []; + my @pldirs = ('src/pl/plpgsql/src'); + push @pldirs, "src/pl/plperl" if $config->{perl}; + push @pldirs, "src/pl/plpython" if $config->{python}; + push @pldirs, "src/pl/tcl" if $config->{tcl}; + File::Find::find( + { + wanted => sub { + /^(.*--.*\.sql|.*\.control)\z/s + && push(@$pl_extension_files, $File::Find::name); + + # Don't find files of in-tree temporary installations. + $_ eq 'share' and $File::Find::prune = 1; + } + }, + @pldirs); + CopySetOfFiles('PL Extension files', + $pl_extension_files, $target . '/share/extension/'); + } + + GenerateNLSFiles($target, $config->{nls}, $majorver) if ($config->{nls}); + + print "Installation complete.\n"; + return; +} + +sub EnsureDirectories +{ + my $target = shift; + mkdir $target unless -d ($target); + while (my $d = shift) + { + mkdir $target . '/' . $d unless -d ($target . '/' . $d); + } + return; +} + +sub CopyFiles +{ + my $what = shift; + my $target = shift; + my $basedir = shift; + + print "Copying $what"; + while (my $f = shift) + { + print "."; + $f = $basedir . $f; + die "No file $f\n" if (!-f $f); + lcopy($f, $target . basename($f)) || croak "Could not copy $f: $!\n"; + } + print "\n"; + return; +} + +sub CopySetOfFiles +{ + my $what = shift; + my $flist = shift; + my $target = shift; + print "Copying $what" if $what; + foreach (@$flist) + { + my $tgt = $target . basename($_); + print "."; + lcopy($_, $tgt) || croak "Could not copy $_: $!\n"; + } + print "\n"; + return; +} + +sub CopySolutionOutput +{ + my $conf = shift; + my $target = shift; + my $rem = + qr{Project\("\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942\}"\) = "([^"]+)"}; + + my $sln = read_file("pgsql.sln") || croak "Could not open pgsql.sln\n"; + + my $vcproj = 'vcproj'; + if ($sln =~ + /Microsoft Visual Studio Solution File, Format Version (\d+)\.\d+/ + && $1 >= 11) + { + $vcproj = 'vcxproj'; + } + + print "Copying build output files..."; + while ($sln =~ $rem) + { + my $pf = $1; + + # Hash-of-arrays listing where to install things. For each + # subdirectory there's a hash key, and the value is an array + # of file extensions to install in that subdirectory. Example: + # { 'bin' => [ 'dll', 'lib' ], + # 'lib' => [ 'lib' ] } + my %install_list; + my $is_sharedlib = 0; + + $sln =~ s/$rem//; + + next + if ($insttype eq "client" && !grep { $_ eq $pf } + @client_program_files); + + my $proj = read_file("$pf.$vcproj") + || croak "Could not open $pf.$vcproj\n"; + + # Check if this project uses a shared library by looking if + # SO_MAJOR_VERSION is defined in its Makefile, whose path + # can be found using the resource file of this project. + if (( $vcproj eq 'vcxproj' + && $proj =~ qr{ResourceCompile\s*Include="([^"]+)"}) + || ( $vcproj eq 'vcproj' + && $proj =~ qr{File\s*RelativePath="([^\"]+)\.rc"})) + { + my $projpath = dirname($1); + my $mfname = + -e "$projpath/GNUmakefile" + ? "$projpath/GNUmakefile" + : "$projpath/Makefile"; + my $mf = read_file($mfname) || croak "Could not open $mfname\n"; + + $is_sharedlib = 1 if ($mf =~ /^SO_MAJOR_VERSION\s*=\s*(.*)$/mg); + } + + if ($vcproj eq 'vcproj' && $proj =~ qr{ConfigurationType="([^"]+)"}) + { + if ($1 == 1) + { + push(@{ $install_list{'bin'} }, "exe"); + } + elsif ($1 == 2) + { + push(@{ $install_list{'lib'} }, "dll"); + if ($is_sharedlib) + { + push(@{ $install_list{'bin'} }, "dll"); + push(@{ $install_list{'lib'} }, "lib"); + } + } + else + { + + # Static libraries, such as libpgport, only used internally + # during build, don't install. + next; + } + } + elsif ($vcproj eq 'vcxproj' + && $proj =~ qr{<ConfigurationType>(\w+)</ConfigurationType>}) + { + if ($1 eq 'Application') + { + push(@{ $install_list{'bin'} }, "exe"); + } + elsif ($1 eq 'DynamicLibrary') + { + push(@{ $install_list{'lib'} }, "dll"); + if ($is_sharedlib) + { + push(@{ $install_list{'bin'} }, "dll"); + push(@{ $install_list{'lib'} }, "lib"); + } + } + else # 'StaticLibrary' + { + + # Static lib, such as libpgport, only used internally + # during build, don't install. + next; + } + } + else + { + croak "Could not parse $pf.$vcproj\n"; + } + + # Install each element + foreach my $dir (keys %install_list) + { + foreach my $ext (@{ $install_list{$dir} }) + { + lcopy("$conf\\$pf\\$pf.$ext", "$target\\$dir\\$pf.$ext") + || croak "Could not copy $pf.$ext\n"; + } + } + lcopy("$conf\\$pf\\$pf.pdb", "$target\\symbols\\$pf.pdb") + || croak "Could not copy $pf.pdb\n"; + print "."; + } + print "\n"; + return; +} + +sub GenerateTimezoneFiles +{ + my $target = shift; + my $conf = shift; + my $mf = read_file("src/timezone/Makefile"); + $mf =~ s{\\\r?\n}{}g; + + $mf =~ /^TZDATAFILES\s*:?=\s*(.*)$/m + || die "Could not find TZDATAFILES line in timezone makefile\n"; + my @tzfiles = split /\s+/, $1; + + print "Generating timezone files..."; + + my @args = ("$conf/zic/zic", '-d', "$target/share/timezone"); + foreach (@tzfiles) + { + my $tzfile = $_; + $tzfile =~ s|\$\(srcdir\)|src/timezone|; + push(@args, $tzfile); + } + + system(@args); + print "\n"; + return; +} + +sub GenerateTsearchFiles +{ + my $target = shift; + + print "Generating tsearch script..."; + my $F; + my $tmpl = read_file('src/backend/snowball/snowball.sql.in'); + my $mf = read_file('src/backend/snowball/Makefile'); + $mf =~ s{\\\r?\n}{}g; + $mf =~ /^LANGUAGES\s*=\s*(.*)$/m + || die "Could not find LANGUAGES line in snowball Makefile\n"; + my @pieces = split /\s+/, $1; + open($F, '>', "$target/share/snowball_create.sql") + || die "Could not write snowball_create.sql"; + print $F read_file('src/backend/snowball/snowball_func.sql.in'); + + while ($#pieces > 0) + { + my $lang = shift @pieces || last; + my $asclang = shift @pieces || last; + my $txt = $tmpl; + my $stop = ''; + + if (-s "src/backend/snowball/stopwords/$lang.stop") + { + $stop = ", StopWords=$lang"; + } + + $txt =~ s#_LANGNAME_#${lang}#gs; + $txt =~ s#_DICTNAME_#${lang}_stem#gs; + $txt =~ s#_CFGNAME_#${lang}#gs; + $txt =~ s#_ASCDICTNAME_#${asclang}_stem#gs; + $txt =~ s#_NONASCDICTNAME_#${lang}_stem#gs; + $txt =~ s#_STOPWORDS_#$stop#gs; + print $F $txt; + print "."; + } + close($F); + print "\n"; + return; +} + +sub CopyContribFiles +{ + my $config = shift; + my $target = shift; + + print "Copying contrib data files..."; + foreach my $subdir ('contrib', 'src/test/modules') + { + my $D; + opendir($D, $subdir) || croak "Could not opendir on $subdir!\n"; + while (my $d = readdir($D)) + { + # These configuration-based exclusions must match vcregress.pl + next if ($d eq "uuid-ossp" && !defined($config->{uuid})); + next if ($d eq "sslinfo" && !defined($config->{openssl})); + next if ($d eq "xml2" && !defined($config->{xml})); + next if ($d =~ /_plperl$/ && !defined($config->{perl})); + next if ($d =~ /_plpython$/ && !defined($config->{python})); + next if ($d eq "sepgsql"); + + CopySubdirFiles($subdir, $d, $config, $target); + } + } + print "\n"; + return; +} + +sub CopySubdirFiles +{ + my $subdir = shift; + my $module = shift; + my $config = shift; + my $target = shift; + + return if ($module =~ /^\./); + return unless (-f "$subdir/$module/Makefile"); + return + if ($insttype eq "client" && !grep { $_ eq $module } @client_contribs); + + my $mf = read_file("$subdir/$module/Makefile"); + $mf =~ s{\\\r?\n}{}g; + + # Note: we currently don't support setting MODULEDIR in the makefile + my $moduledir = 'contrib'; + + my $flist = ''; + if ($mf =~ /^EXTENSION\s*=\s*(.*)$/m) { $flist .= $1 } + if ($flist ne '') + { + $moduledir = 'extension'; + $flist = ParseAndCleanRule($flist, $mf); + + foreach my $f (split /\s+/, $flist) + { + lcopy("$subdir/$module/$f.control", + "$target/share/extension/$f.control") + || croak("Could not copy file $f.control in contrib $module"); + print '.'; + } + } + + $flist = ''; + if ($mf =~ /^DATA_built\s*=\s*(.*)$/m) { $flist .= $1 } + if ($mf =~ /^DATA\s*=\s*(.*)$/m) { $flist .= " $1" } + $flist =~ s/^\s*//; # Remove leading spaces if we had only DATA_built + + if ($flist ne '') + { + $flist = ParseAndCleanRule($flist, $mf); + + foreach my $f (split /\s+/, $flist) + { + lcopy("$subdir/$module/$f", + "$target/share/$moduledir/" . basename($f)) + || croak("Could not copy file $f in contrib $module"); + print '.'; + } + } + + $flist = ''; + if ($mf =~ /^DATA_TSEARCH\s*=\s*(.*)$/m) { $flist .= $1 } + if ($flist ne '') + { + $flist = ParseAndCleanRule($flist, $mf); + + foreach my $f (split /\s+/, $flist) + { + lcopy("$subdir/$module/$f", + "$target/share/tsearch_data/" . basename($f)) + || croak("Could not copy file $f in $subdir $module"); + print '.'; + } + } + + { + $flist = ''; + if ($mf =~ /^HEADERS\s*=\s*(.*)$/m) { $flist .= $1 } + my @modlist = (); + my %fmodlist = (); + while ($mf =~ /^HEADERS_([^\s=]+)\s*=\s*(.*)$/mg) + { + $fmodlist{$1} .= $2; + } + + if ($mf =~ /^MODULE_big\s*=\s*(.*)$/m) + { + push @modlist, $1; + if ($flist ne '') + { + $fmodlist{$1} = $flist; + $flist = ''; + } + } + elsif ($mf =~ /^MODULES\s*=\s*(.*)$/m) + { + push @modlist, split /\s+/, $1; + } + + croak "HEADERS requires MODULE_big in $subdir $module" + if $flist ne ''; + + foreach my $mod (keys %fmodlist) + { + croak "HEADERS_$mod for unknown module in $subdir $module" + unless grep { $_ eq $mod } @modlist; + $flist = ParseAndCleanRule($fmodlist{$mod}, $mf); + EnsureDirectories($target, "include", "include/server", + "include/server/$moduledir", + "include/server/$moduledir/$mod"); + foreach my $f (split /\s+/, $flist) + { + lcopy("$subdir/$module/$f", + "$target/include/server/$moduledir/$mod/" . basename($f)) + || croak("Could not copy file $f in $subdir $module"); + print '.'; + } + } + } + + $flist = ''; + if ($mf =~ /^DOCS\s*=\s*(.*)$/mg) { $flist .= $1 } + if ($flist ne '') + { + $flist = ParseAndCleanRule($flist, $mf); + + # Special case for contrib/spi + $flist = + "autoinc.example insert_username.example moddatetime.example refint.example" + if ($module eq 'spi'); + foreach my $f (split /\s+/, $flist) + { + lcopy("$subdir/$module/$f", "$target/doc/$moduledir/$f") + || croak("Could not copy file $f in contrib $module"); + print '.'; + } + } + return; +} + +sub ParseAndCleanRule +{ + my $flist = shift; + my $mf = shift; + + # Strip out $(addsuffix) rules + if (index($flist, '$(addsuffix ') >= 0) + { + my $pcount = 0; + my $i; + for ( + $i = index($flist, '$(addsuffix ') + 12; + $i < length($flist); + $i++) + { + $pcount++ if (substr($flist, $i, 1) eq '('); + $pcount-- if (substr($flist, $i, 1) eq ')'); + last if ($pcount < 0); + } + $flist = + substr($flist, 0, index($flist, '$(addsuffix ')) + . substr($flist, $i + 1); + } + return $flist; +} + +sub CopyIncludeFiles +{ + my $target = shift; + + EnsureDirectories($target, 'include', 'include/libpq', 'include/internal', + 'include/internal/libpq', 'include/server', 'include/server/parser'); + + CopyFiles( + 'Public headers', $target . '/include/', + 'src/include/', 'postgres_ext.h', + 'pg_config.h', 'pg_config_ext.h', + 'pg_config_os.h', 'pg_config_manual.h'); + lcopy('src/include/libpq/libpq-fs.h', $target . '/include/libpq/') + || croak 'Could not copy libpq-fs.h'; + + CopyFiles( + 'Libpq headers', + $target . '/include/', + 'src/interfaces/libpq/', 'libpq-fe.h', 'libpq-events.h'); + CopyFiles( + 'Libpq internal headers', + $target . '/include/internal/', + 'src/interfaces/libpq/', 'libpq-int.h', 'pqexpbuffer.h'); + + CopyFiles( + 'Internal headers', + $target . '/include/internal/', + 'src/include/', 'c.h', 'port.h', 'postgres_fe.h'); + lcopy('src/include/libpq/pqcomm.h', $target . '/include/internal/libpq/') + || croak 'Could not copy pqcomm.h'; + + CopyFiles( + 'Server headers', + $target . '/include/server/', + 'src/include/', 'pg_config.h', 'pg_config_ext.h', 'pg_config_os.h'); + CopyFiles( + 'Grammar header', + $target . '/include/server/parser/', + 'src/backend/parser/', 'gram.h'); + CopySetOfFiles( + '', + [ glob("src\\include\\*.h") ], + $target . '/include/server/'); + my $D; + opendir($D, 'src/include') || croak "Could not opendir on src/include!\n"; + + CopyFiles( + 'PL/pgSQL header', + $target . '/include/server/', + 'src/pl/plpgsql/src/', 'plpgsql.h'); + + # some xcopy progs don't like mixed slash style paths + (my $ctarget = $target) =~ s!/!\\!g; + while (my $d = readdir($D)) + { + next if ($d =~ /^\./); + next if ($d eq '.git'); + next if ($d eq 'CVS'); + next unless (-d "src/include/$d"); + + EnsureDirectories("$target/include/server/$d"); + my @args = ( + 'xcopy', '/s', '/i', '/q', '/r', '/y', "src\\include\\$d\\*.h", + "$ctarget\\include\\server\\$d\\"); + system(@args) && croak("Failed to copy include directory $d\n"); + } + closedir($D); + + my $mf = read_file('src/interfaces/ecpg/include/Makefile'); + $mf =~ s{\\\r?\n}{}g; + $mf =~ /^ecpg_headers\s*=\s*(.*)$/m + || croak "Could not find ecpg_headers line\n"; + CopyFiles( + 'ECPG headers', + $target . '/include/', + 'src/interfaces/ecpg/include/', + 'ecpg_config.h', split /\s+/, $1); + $mf =~ /^informix_headers\s*=\s*(.*)$/m + || croak "Could not find informix_headers line\n"; + EnsureDirectories($target . '/include', 'informix', 'informix/esql'); + CopyFiles( + 'ECPG informix headers', + $target . '/include/informix/esql/', + 'src/interfaces/ecpg/include/', + split /\s+/, $1); + return; +} + +sub GenerateNLSFiles +{ + my $target = shift; + my $nlspath = shift; + my $majorver = shift; + + print "Installing NLS files..."; + EnsureDirectories($target, "share/locale"); + my @flist; + File::Find::find( + { + wanted => sub { + /^nls\.mk\z/s + && !push(@flist, $File::Find::name); + } + }, + "src"); + foreach (@flist) + { + my $prgm = DetermineCatalogName($_); + s/nls.mk/po/; + my $dir = $_; + next unless ($dir =~ /([^\/]+)\/po$/); + foreach (glob("$dir/*.po")) + { + my $lang; + next unless /([^\/]+)\.po/; + $lang = $1; + + EnsureDirectories($target, "share/locale/$lang", + "share/locale/$lang/LC_MESSAGES"); + my @args = ( + "$nlspath\\bin\\msgfmt", + '-o', + "$target\\share\\locale\\$lang\\LC_MESSAGES\\$prgm-$majorver.mo", + $_); + system(@args) && croak("Could not run msgfmt on $dir\\$_"); + print "."; + } + } + print "\n"; + return; +} + +sub DetermineMajorVersion +{ + my $f = read_file('src/include/pg_config.h') + || croak 'Could not open pg_config.h'; + $f =~ /^#define\s+PG_MAJORVERSION\s+"([^"]+)"/m + || croak 'Could not determine major version'; + return $1; +} + +sub DetermineCatalogName +{ + my $filename = shift; + + my $f = read_file($filename) || croak "Could not open $filename"; + $f =~ /CATALOG_NAME\s*\:?=\s*(\S+)/m + || croak "Could not determine catalog name in $filename"; + return $1; +} + +sub read_file +{ + my $filename = shift; + my $F; + local $/ = undef; + open($F, '<', $filename) || die "Could not open file $filename\n"; + my $txt = <$F>; + close($F); + + return $txt; +} + +1; |