diff options
Diffstat (limited to 'src/tools/msvc')
27 files changed, 6207 insertions, 0 deletions
diff --git a/src/tools/msvc/.gitignore b/src/tools/msvc/.gitignore new file mode 100644 index 0000000..2470e78 --- /dev/null +++ b/src/tools/msvc/.gitignore @@ -0,0 +1,3 @@ +# Custom configuration files for MSVC build +/config.pl +/buildenv.pl 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; diff --git a/src/tools/msvc/MSBuildProject.pm b/src/tools/msvc/MSBuildProject.pm new file mode 100644 index 0000000..33595cc --- /dev/null +++ b/src/tools/msvc/MSBuildProject.pm @@ -0,0 +1,536 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package MSBuildProject; + +# +# Package that encapsulates a MSBuild project file (Visual C++ 2013 or greater) +# +# src/tools/msvc/MSBuildProject.pm +# + +use Carp; +use strict; +use warnings; +use base qw(Project); + +no warnings qw(redefine); ## no critic + +sub _new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{filenameExtension} = '.vcxproj'; + $self->{ToolsVersion} = '4.0'; + + return $self; +} + +sub WriteHeader +{ + my ($self, $f) = @_; + + print $f <<EOF; +<?xml version="1.0" encoding="Windows-1252"?> +<Project DefaultTargets="Build" ToolsVersion="$self->{ToolsVersion}" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> +EOF + $self->WriteConfigurationHeader($f, 'Debug'); + $self->WriteConfigurationHeader($f, 'Release'); + print $f <<EOF; + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>$self->{guid}</ProjectGuid> +EOF + # Check whether WindowsSDKVersion env variable is present. + # Add WindowsTargetPlatformVersion node if so. + my $sdkVersion = $ENV{'WindowsSDKVersion'}; + if (defined($sdkVersion)) + { + # remove trailing backslash if necessary. + $sdkVersion =~ s/\\$//; + print $f <<EOF + <WindowsTargetPlatformVersion>$sdkVersion</WindowsTargetPlatformVersion> +EOF + } + print $f <<EOF; + </PropertyGroup> + <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.Default.props" /> +EOF + $self->WriteConfigurationPropertyGroup($f, 'Release', + { wholeopt => 'false' }); + $self->WriteConfigurationPropertyGroup($f, 'Debug', + { wholeopt => 'false' }); + print $f <<EOF; + <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> +EOF + $self->WritePropertySheetsPropertyGroup($f, 'Release'); + $self->WritePropertySheetsPropertyGroup($f, 'Debug'); + print $f <<EOF; + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion> +EOF + $self->WriteAdditionalProperties($f, 'Debug'); + $self->WriteAdditionalProperties($f, 'Release'); + print $f <<EOF; + </PropertyGroup> +EOF + + $self->WriteItemDefinitionGroup( + $f, 'Debug', + { + defs => "_DEBUG;DEBUG=1", + opt => 'Disabled', + strpool => 'false', + runtime => 'MultiThreadedDebugDLL' + }); + $self->WriteItemDefinitionGroup( + $f, + 'Release', + { + defs => "", + opt => 'Full', + strpool => 'true', + runtime => 'MultiThreadedDLL' + }); + return; +} + +sub AddDefine +{ + my ($self, $def) = @_; + + $self->{defines} .= $def . ';'; + return; +} + +sub WriteReferences +{ + my ($self, $f) = @_; + + my @references = @{ $self->{references} }; + + if (scalar(@references)) + { + print $f <<EOF; + <ItemGroup> +EOF + foreach my $ref (@references) + { + print $f <<EOF; + <ProjectReference Include="$ref->{name}$ref->{filenameExtension}"> + <Project>$ref->{guid}</Project> + </ProjectReference> +EOF + } + print $f <<EOF; + </ItemGroup> +EOF + } + return; +} + +sub WriteFiles +{ + my ($self, $f) = @_; + print $f <<EOF; + <ItemGroup> +EOF + my @grammarFiles = (); + my @resourceFiles = (); + my %uniquefiles; + foreach my $fileNameWithPath (sort keys %{ $self->{files} }) + { + confess "Bad format filename '$fileNameWithPath'\n" + unless ($fileNameWithPath =~ m!^(.*)/([^/]+)\.(c|cpp|y|l|rc)$!); + my $dir = $1; + my $fileName = $2; + if ($fileNameWithPath =~ /\.y$/ or $fileNameWithPath =~ /\.l$/) + { + push @grammarFiles, $fileNameWithPath; + } + elsif ($fileNameWithPath =~ /\.rc$/) + { + push @resourceFiles, $fileNameWithPath; + } + elsif (defined($uniquefiles{$fileName})) + { + + # File already exists, so fake a new name + my $obj = $dir; + $obj =~ s!/!_!g; + + print $f <<EOF; + <ClCompile Include="$fileNameWithPath"> + <ObjectFileName Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">.\\debug\\$self->{name}\\${obj}_$fileName.obj</ObjectFileName> + <ObjectFileName Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">.\\release\\$self->{name}\\${obj}_$fileName.obj</ObjectFileName> + </ClCompile> +EOF + } + else + { + $uniquefiles{$fileName} = 1; + print $f <<EOF; + <ClCompile Include="$fileNameWithPath" /> +EOF + } + + } + print $f <<EOF; + </ItemGroup> +EOF + if (scalar(@grammarFiles)) + { + print $f <<EOF; + <ItemGroup> +EOF + foreach my $grammarFile (@grammarFiles) + { + (my $outputFile = $grammarFile) =~ s/\.(y|l)$/.c/; + if ($grammarFile =~ /\.y$/) + { + $outputFile =~ + s{^src\\pl\\plpgsql\\src\\gram.c$}{src\\pl\\plpgsql\\src\\pl_gram.c}; + print $f <<EOF; + <CustomBuild Include="$grammarFile"> + <Message Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">Running bison on $grammarFile</Message> + <Command Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">perl "src\\tools\\msvc\\pgbison.pl" "$grammarFile"</Command> + <AdditionalInputs Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">%(AdditionalInputs)</AdditionalInputs> + <Outputs Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">$outputFile;%(Outputs)</Outputs> + <Message Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">Running bison on $grammarFile</Message> + <Command Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">perl "src\\tools\\msvc\\pgbison.pl" "$grammarFile"</Command> + <AdditionalInputs Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">%(AdditionalInputs)</AdditionalInputs> + <Outputs Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">$outputFile;%(Outputs)</Outputs> + </CustomBuild> +EOF + } + else #if ($grammarFile =~ /\.l$/) + { + print $f <<EOF; + <CustomBuild Include="$grammarFile"> + <Message Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">Running flex on $grammarFile</Message> + <Command Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">perl "src\\tools\\msvc\\pgflex.pl" "$grammarFile"</Command> + <AdditionalInputs Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">%(AdditionalInputs)</AdditionalInputs> + <Outputs Condition="'\$(Configuration)|\$(Platform)'=='Debug|$self->{platform}'">$outputFile;%(Outputs)</Outputs> + <Message Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">Running flex on $grammarFile</Message> + <Command Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">perl "src\\tools\\msvc\\pgflex.pl" "$grammarFile"</Command> + <AdditionalInputs Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">%(AdditionalInputs)</AdditionalInputs> + <Outputs Condition="'\$(Configuration)|\$(Platform)'=='Release|$self->{platform}'">$outputFile;%(Outputs)</Outputs> + </CustomBuild> +EOF + } + } + print $f <<EOF; + </ItemGroup> +EOF + } + if (scalar(@resourceFiles)) + { + print $f <<EOF; + <ItemGroup> +EOF + foreach my $rcFile (@resourceFiles) + { + print $f <<EOF; + <ResourceCompile Include="$rcFile" /> +EOF + } + print $f <<EOF; + </ItemGroup> +EOF + } + return; +} + +sub WriteConfigurationHeader +{ + my ($self, $f, $cfgname) = @_; + print $f <<EOF; + <ProjectConfiguration Include="$cfgname|$self->{platform}"> + <Configuration>$cfgname</Configuration> + <Platform>$self->{platform}</Platform> + </ProjectConfiguration> +EOF + return; +} + +sub WriteConfigurationPropertyGroup +{ + my ($self, $f, $cfgname, $p) = @_; + my $cfgtype = + ($self->{type} eq "exe") + ? 'Application' + : ($self->{type} eq "dll" ? 'DynamicLibrary' : 'StaticLibrary'); + + print $f <<EOF; + <PropertyGroup Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'" Label="Configuration"> + <ConfigurationType>$cfgtype</ConfigurationType> + <UseOfMfc>false</UseOfMfc> + <CharacterSet>MultiByte</CharacterSet> + <WholeProgramOptimization>$p->{wholeopt}</WholeProgramOptimization> + <PlatformToolset>$self->{PlatformToolset}</PlatformToolset> + </PropertyGroup> +EOF + return; +} + +sub WritePropertySheetsPropertyGroup +{ + my ($self, $f, $cfgname) = @_; + print $f <<EOF; + <ImportGroup Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'" Label="PropertySheets"> + <Import Project="\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props" Condition="exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> +EOF + return; +} + +sub WriteAdditionalProperties +{ + my ($self, $f, $cfgname) = @_; + print $f <<EOF; + <OutDir Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'">.\\$cfgname\\$self->{name}\\</OutDir> + <IntDir Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'">.\\$cfgname\\$self->{name}\\</IntDir> + <LinkIncremental Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'">false</LinkIncremental> +EOF + return; +} + +sub WriteItemDefinitionGroup +{ + my ($self, $f, $cfgname, $p) = @_; + my $cfgtype = + ($self->{type} eq "exe") + ? 'Application' + : ($self->{type} eq "dll" ? 'DynamicLibrary' : 'StaticLibrary'); + my $libs = $self->GetAdditionalLinkerDependencies($cfgname, ';'); + + my $targetmachine = + $self->{platform} eq 'Win32' ? 'MachineX86' : 'MachineX64'; + + my $includes = $self->{includes}; + unless ($includes eq '' or $includes =~ /;$/) + { + $includes .= ';'; + } + print $f <<EOF; + <ItemDefinitionGroup Condition="'\$(Configuration)|\$(Platform)'=='$cfgname|$self->{platform}'"> + <ClCompile> + <Optimization>$p->{opt}</Optimization> + <AdditionalIncludeDirectories>$self->{prefixincludes}src/include;src/include/port/win32;src/include/port/win32_msvc;$includes\%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_WINDOWS;__WINDOWS__;__WIN32__;WIN32_STACK_RLIMIT=4194304;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE$self->{defines}$p->{defs}\%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>$p->{strpool}</StringPooling> + <RuntimeLibrary>$p->{runtime}</RuntimeLibrary> + <DisableSpecificWarnings>$self->{disablewarnings};\%(DisableSpecificWarnings)</DisableSpecificWarnings> + <AdditionalOptions>/MP \%(AdditionalOptions)</AdditionalOptions> + <AssemblerOutput> + </AssemblerOutput> + <AssemblerListingLocation>.\\$cfgname\\$self->{name}\\</AssemblerListingLocation> + <ObjectFileName>.\\$cfgname\\$self->{name}\\</ObjectFileName> + <ProgramDataBaseFileName>.\\$cfgname\\$self->{name}\\</ProgramDataBaseFileName> + <BrowseInformation>false</BrowseInformation> + <WarningLevel>Level3</WarningLevel> + <SuppressStartupBanner>true</SuppressStartupBanner> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <CompileAs>Default</CompileAs> + </ClCompile> + <Link> + <OutputFile>.\\$cfgname\\$self->{name}\\$self->{name}.$self->{type}</OutputFile> + <AdditionalDependencies>$libs;\%(AdditionalDependencies)</AdditionalDependencies> + <SuppressStartupBanner>true</SuppressStartupBanner> + <AdditionalLibraryDirectories>\%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <IgnoreSpecificDefaultLibraries>libc;\%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries> + <StackReserveSize>4194304</StackReserveSize> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>.\\$cfgname\\$self->{name}\\$self->{name}.pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapFileName>.\\$cfgname\\$self->{name}\\$self->{name}.map</MapFileName> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <!-- Permit links to MinGW-built, 32-bit DLLs (default before VS2012). --> + <ImageHasSafeExceptionHandlers/> + <SubSystem>Console</SubSystem> + <TargetMachine>$targetmachine</TargetMachine> +EOF + if ($self->{disablelinkerwarnings}) + { + print $f + " <AdditionalOptions>/ignore:$self->{disablelinkerwarnings} \%(AdditionalOptions)</AdditionalOptions>\n"; + } + if ($self->{implib}) + { + my $l = $self->{implib}; + $l =~ s/__CFGNAME__/$cfgname/g; + print $f " <ImportLibrary>$l</ImportLibrary>\n"; + } + if ($self->{def}) + { + my $d = $self->{def}; + $d =~ s/__CFGNAME__/$cfgname/g; + print $f " <ModuleDefinitionFile>$d</ModuleDefinitionFile>\n"; + } + print $f <<EOF; + </Link> + <ResourceCompile> + <AdditionalIncludeDirectories>src\\include;\%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ResourceCompile> +EOF + if ($self->{builddef}) + { + print $f <<EOF; + <PreLinkEvent> + <Message>Generate DEF file</Message> + <Command>perl src\\tools\\msvc\\gendef.pl $cfgname\\$self->{name} $self->{platform}</Command> + </PreLinkEvent> +EOF + } + print $f <<EOF; + </ItemDefinitionGroup> +EOF + return; +} + +sub Footer +{ + my ($self, $f) = @_; + $self->WriteReferences($f); + + print $f <<EOF; + <Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> +EOF + return; +} + +package VC2013Project; + +# +# Package that encapsulates a Visual C++ 2013 project file +# + +use strict; +use warnings; +use base qw(MSBuildProject); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{vcver} = '12.00'; + $self->{PlatformToolset} = 'v120'; + $self->{ToolsVersion} = '12.0'; + + return $self; +} + +package VC2015Project; + +# +# Package that encapsulates a Visual C++ 2015 project file +# + +use strict; +use warnings; +use base qw(MSBuildProject); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{vcver} = '14.00'; + $self->{PlatformToolset} = 'v140'; + $self->{ToolsVersion} = '14.0'; + + return $self; +} + +package VC2017Project; + +# +# Package that encapsulates a Visual C++ 2017 project file +# + +use strict; +use warnings; +use base qw(MSBuildProject); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{vcver} = '15.00'; + $self->{PlatformToolset} = 'v141'; + $self->{ToolsVersion} = '15.0'; + + return $self; +} + +package VC2019Project; + +# +# Package that encapsulates a Visual C++ 2019 project file +# + +use strict; +use warnings; +use base qw(MSBuildProject); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{vcver} = '16.00'; + $self->{PlatformToolset} = 'v142'; + $self->{ToolsVersion} = '16.0'; + + return $self; +} + +package VC2022Project; + +# +# Package that encapsulates a Visual C++ 2022 project file +# + +use strict; +use warnings; +use base qw(MSBuildProject); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{vcver} = '17.00'; + $self->{PlatformToolset} = 'v143'; + $self->{ToolsVersion} = '17.0'; + + return $self; +} + +1; diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm new file mode 100644 index 0000000..05ff67e --- /dev/null +++ b/src/tools/msvc/Mkvcbuild.pm @@ -0,0 +1,1133 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Mkvcbuild; + +# +# Package that generates build files for msvc build +# +# src/tools/msvc/Mkvcbuild.pm +# +use strict; +use warnings; + +use Carp; +use if ($^O eq "MSWin32"), 'Win32'; +use Project; +use Solution; +use Cwd; +use File::Copy; +use Config; +use VSObjectFactory; +use List::Util qw(first); + +use Exporter; +our (@ISA, @EXPORT_OK); +@ISA = qw(Exporter); +@EXPORT_OK = qw(Mkvcbuild); + +my $solution; +my $libpgport; +my $libpgcommon; +my $libpgfeutils; +my $postgres; +my $libpq; +my @unlink_on_exit; + +# Set of variables for modules in contrib/ and src/test/modules/ +my $contrib_defines = { 'refint' => 'REFINT_VERBOSE' }; +my @contrib_uselibpq = + ('dblink', 'oid2name', 'postgres_fdw', 'vacuumlo', 'libpq_pipeline'); +my @contrib_uselibpgport = ('libpq_pipeline', 'oid2name', 'vacuumlo'); +my @contrib_uselibpgcommon = ('libpq_pipeline', 'oid2name', 'vacuumlo'); +my $contrib_extralibs = { 'libpq_pipeline' => ['ws2_32.lib'] }; +my $contrib_extraincludes = { 'dblink' => ['src/backend'] }; +my $contrib_extrasource = { + 'cube' => [ 'contrib/cube/cubescan.l', 'contrib/cube/cubeparse.y' ], + 'seg' => [ 'contrib/seg/segscan.l', 'contrib/seg/segparse.y' ], +}; +my @contrib_excludes = ( + 'bool_plperl', 'commit_ts', + 'hstore_plperl', 'hstore_plpython', + 'intagg', 'jsonb_plperl', + 'jsonb_plpython', 'ltree_plpython', + 'pgcrypto', 'sepgsql', + 'brin', 'test_extensions', + 'test_misc', 'test_pg_dump', + 'snapshot_too_old', 'unsafe_tests'); + +# Set of variables for frontend modules +my $frontend_defines = { 'initdb' => 'FRONTEND' }; +my @frontend_uselibpq = + ('pg_amcheck', 'pg_ctl', 'pg_upgrade', 'pgbench', 'psql', 'initdb'); +my @frontend_uselibpgport = ( + 'pg_amcheck', 'pg_archivecleanup', + 'pg_test_fsync', 'pg_test_timing', + 'pg_upgrade', 'pg_waldump', + 'pgbench'); +my @frontend_uselibpgcommon = ( + 'pg_amcheck', 'pg_archivecleanup', + 'pg_test_fsync', 'pg_test_timing', + 'pg_upgrade', 'pg_waldump', + 'pgbench'); +my $frontend_extralibs = { + 'initdb' => ['ws2_32.lib'], + 'pg_amcheck' => ['ws2_32.lib'], + 'pg_restore' => ['ws2_32.lib'], + 'pgbench' => ['ws2_32.lib'], + 'psql' => ['ws2_32.lib'] +}; +my $frontend_extraincludes = { + 'initdb' => ['src/timezone'], + 'psql' => ['src/backend'] +}; +my $frontend_extrasource = { + 'psql' => ['src/bin/psql/psqlscanslash.l'], + 'pgbench' => + [ 'src/bin/pgbench/exprscan.l', 'src/bin/pgbench/exprparse.y' ] +}; +my @frontend_excludes = ( + 'pgevent', 'pg_basebackup', 'pg_rewind', 'pg_dump', + 'pg_waldump', 'scripts'); + +sub mkvcbuild +{ + our $config = shift; + + chdir('../../..') if (-d '../msvc' && -d '../../../src'); + die 'Must run from root or msvc directory' + unless (-d 'src/tools/msvc' && -d 'src'); + + my $vsVersion = DetermineVisualStudioVersion(); + + $solution = CreateSolution($vsVersion, $config); + + our @pgportfiles = qw( + chklocale.c explicit_bzero.c fls.c getpeereid.c getrusage.c inet_aton.c random.c + srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c + erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c + dirent.c dlopen.c getopt.c getopt_long.c link.c + pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c + pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c + pqsignal.c mkdtemp.c qsort.c qsort_arg.c bsearch_arg.c quotes.c system.c + strerror.c tar.c thread.c + win32env.c win32error.c win32security.c win32setlocale.c win32stat.c); + + push(@pgportfiles, 'strtof.c') if ($vsVersion < '14.00'); + + if ($vsVersion >= '9.00') + { + push(@pgportfiles, 'pg_crc32c_sse42_choose.c'); + push(@pgportfiles, 'pg_crc32c_sse42.c'); + push(@pgportfiles, 'pg_crc32c_sb8.c'); + } + else + { + push(@pgportfiles, 'pg_crc32c_sb8.c'); + } + + our @pgcommonallfiles = qw( + archive.c base64.c checksum_helper.c + config_info.c controldata_utils.c d2s.c encnames.c exec.c + f2s.c file_perm.c file_utils.c hashfn.c ip.c jsonapi.c + keywords.c kwlookup.c link-canary.c md5_common.c + pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c + saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c + wait_error.c wchar.c); + + if ($solution->{options}->{openssl}) + { + push(@pgcommonallfiles, 'cryptohash_openssl.c'); + push(@pgcommonallfiles, 'hmac_openssl.c'); + push(@pgcommonallfiles, 'protocol_openssl.c'); + } + else + { + push(@pgcommonallfiles, 'cryptohash.c'); + push(@pgcommonallfiles, 'hmac.c'); + push(@pgcommonallfiles, 'md5.c'); + push(@pgcommonallfiles, 'sha1.c'); + push(@pgcommonallfiles, 'sha2.c'); + } + + our @pgcommonfrontendfiles = ( + @pgcommonallfiles, qw(fe_memutils.c + logging.c restricted_token.c sprompt.c)); + + our @pgcommonbkndfiles = @pgcommonallfiles; + + our @pgfeutilsfiles = qw( + archive.c cancel.c conditional.c connect_utils.c mbprint.c option_utils.c + parallel_slot.c print.c psqlscan.l psqlscan.c query_utils.c simple_list.c + string_utils.c recovery_gen.c); + + $libpgport = $solution->AddProject('libpgport', 'lib', 'misc'); + $libpgport->AddDefine('FRONTEND'); + $libpgport->AddFiles('src/port', @pgportfiles); + + $libpgcommon = $solution->AddProject('libpgcommon', 'lib', 'misc'); + $libpgcommon->AddDefine('FRONTEND'); + $libpgcommon->AddFiles('src/common', @pgcommonfrontendfiles); + + $libpgfeutils = $solution->AddProject('libpgfeutils', 'lib', 'misc'); + $libpgfeutils->AddDefine('FRONTEND'); + $libpgfeutils->AddIncludeDir('src/interfaces/libpq'); + $libpgfeutils->AddFiles('src/fe_utils', @pgfeutilsfiles); + + $postgres = $solution->AddProject('postgres', 'exe', '', 'src/backend'); + $postgres->AddIncludeDir('src/backend'); + $postgres->AddDir('src/backend/port/win32'); + $postgres->AddFile('src/backend/utils/fmgrtab.c'); + $postgres->ReplaceFile('src/backend/port/pg_sema.c', + 'src/backend/port/win32_sema.c'); + $postgres->ReplaceFile('src/backend/port/pg_shmem.c', + 'src/backend/port/win32_shmem.c'); + $postgres->AddFiles('src/port', @pgportfiles); + $postgres->AddFiles('src/common', @pgcommonbkndfiles); + $postgres->AddDir('src/timezone'); + + # We need source files from src/timezone, but that directory's resource + # file pertains to "zic", not to the backend. + $postgres->RemoveFile('src/timezone/win32ver.rc'); + $postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y'); + $postgres->AddFiles('src/backend/bootstrap', 'bootscanner.l', + 'bootparse.y'); + $postgres->AddFiles('src/backend/utils/misc', 'guc-file.l'); + $postgres->AddFiles( + 'src/backend/replication', 'repl_scanner.l', + 'repl_gram.y', 'syncrep_scanner.l', + 'syncrep_gram.y'); + $postgres->AddFiles('src/backend/utils/adt', 'jsonpath_scan.l', + 'jsonpath_gram.y'); + $postgres->AddDefine('BUILDING_DLL'); + $postgres->AddLibrary('secur32.lib'); + $postgres->AddLibrary('ws2_32.lib'); + $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); + $postgres->FullExportDLL('postgres.lib'); + + # The OBJS scraper doesn't know about ifdefs, so remove appropriate files + # if building without OpenSSL. + if (!$solution->{options}->{openssl}) + { + $postgres->RemoveFile('src/backend/libpq/be-secure-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c'); + } + if (!$solution->{options}->{gss}) + { + $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c'); + $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c'); + } + + my $snowball = $solution->AddProject('dict_snowball', 'dll', '', + 'src/backend/snowball'); + + # This Makefile uses VPATH to find most source files in a subdirectory. + $snowball->RelocateFiles( + 'src/backend/snowball/libstemmer', + sub { + return shift !~ /(dict_snowball.c|win32ver.rc)$/; + }); + $snowball->AddIncludeDir('src/include/snowball'); + $snowball->AddReference($postgres); + + my $plpgsql = + $solution->AddProject('plpgsql', 'dll', 'PLs', 'src/pl/plpgsql/src'); + $plpgsql->AddFiles('src/pl/plpgsql/src', 'pl_gram.y'); + $plpgsql->AddReference($postgres); + + if ($solution->{options}->{tcl}) + { + my $found = 0; + my $pltcl = + $solution->AddProject('pltcl', 'dll', 'PLs', 'src/pl/tcl'); + $pltcl->AddIncludeDir($solution->{options}->{tcl} . '/include'); + $pltcl->AddReference($postgres); + + for my $tclver (qw(86t 86 85 84)) + { + my $tcllib = $solution->{options}->{tcl} . "/lib/tcl$tclver.lib"; + if (-e $tcllib) + { + $pltcl->AddLibrary($tcllib); + $found = 1; + last; + } + } + die "Unable to find $solution->{options}->{tcl}/lib/tcl<version>.lib" + unless $found; + } + + $libpq = $solution->AddProject('libpq', 'dll', 'interfaces', + 'src/interfaces/libpq'); + $libpq->AddDefine('FRONTEND'); + $libpq->AddDefine('UNSAFE_STAT_OK'); + $libpq->AddIncludeDir('src/port'); + $libpq->AddLibrary('secur32.lib'); + $libpq->AddLibrary('ws2_32.lib'); + $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); + $libpq->UseDef('src/interfaces/libpq/libpqdll.def'); + $libpq->AddReference($libpgcommon, $libpgport); + + # The OBJS scraper doesn't know about ifdefs, so remove appropriate files + # if building without OpenSSL. + if (!$solution->{options}->{openssl}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c'); + } + if (!$solution->{options}->{gss}) + { + $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c'); + $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c'); + } + + my $libpqwalreceiver = + $solution->AddProject('libpqwalreceiver', 'dll', '', + 'src/backend/replication/libpqwalreceiver'); + $libpqwalreceiver->AddIncludeDir('src/interfaces/libpq'); + $libpqwalreceiver->AddReference($postgres, $libpq); + + my $pgoutput = $solution->AddProject('pgoutput', 'dll', '', + 'src/backend/replication/pgoutput'); + $pgoutput->AddReference($postgres); + + my $pgtypes = $solution->AddProject( + 'libpgtypes', 'dll', + 'interfaces', 'src/interfaces/ecpg/pgtypeslib'); + $pgtypes->AddDefine('FRONTEND'); + $pgtypes->AddReference($libpgcommon, $libpgport); + $pgtypes->UseDef('src/interfaces/ecpg/pgtypeslib/pgtypeslib.def'); + $pgtypes->AddIncludeDir('src/interfaces/ecpg/include'); + + my $libecpg = $solution->AddProject('libecpg', 'dll', 'interfaces', + 'src/interfaces/ecpg/ecpglib'); + $libecpg->AddDefine('FRONTEND'); + $libecpg->AddIncludeDir('src/interfaces/ecpg/include'); + $libecpg->AddIncludeDir('src/interfaces/libpq'); + $libecpg->AddIncludeDir('src/port'); + $libecpg->UseDef('src/interfaces/ecpg/ecpglib/ecpglib.def'); + $libecpg->AddLibrary('ws2_32.lib'); + $libecpg->AddReference($libpq, $pgtypes, $libpgport); + + my $libecpgcompat = $solution->AddProject( + 'libecpg_compat', 'dll', + 'interfaces', 'src/interfaces/ecpg/compatlib'); + $libecpgcompat->AddDefine('FRONTEND'); + $libecpgcompat->AddIncludeDir('src/interfaces/ecpg/include'); + $libecpgcompat->AddIncludeDir('src/interfaces/libpq'); + $libecpgcompat->UseDef('src/interfaces/ecpg/compatlib/compatlib.def'); + $libecpgcompat->AddReference($pgtypes, $libecpg, $libpgport, + $libpgcommon); + + my $ecpg = $solution->AddProject('ecpg', 'exe', 'interfaces', + 'src/interfaces/ecpg/preproc'); + $ecpg->AddIncludeDir('src/interfaces/ecpg/include'); + $ecpg->AddIncludeDir('src/interfaces/ecpg/ecpglib'); + $ecpg->AddIncludeDir('src/interfaces/libpq'); + $ecpg->AddPrefixInclude('src/interfaces/ecpg/preproc'); + $ecpg->AddFiles('src/interfaces/ecpg/preproc', 'pgc.l', 'preproc.y'); + $ecpg->AddReference($libpgcommon, $libpgport); + + my $pgregress_ecpg = + $solution->AddProject('pg_regress_ecpg', 'exe', 'misc'); + $pgregress_ecpg->AddFile('src/interfaces/ecpg/test/pg_regress_ecpg.c'); + $pgregress_ecpg->AddFile('src/test/regress/pg_regress.c'); + $pgregress_ecpg->AddIncludeDir('src/port'); + $pgregress_ecpg->AddIncludeDir('src/test/regress'); + $pgregress_ecpg->AddDefine('HOST_TUPLE="i686-pc-win32vc"'); + $pgregress_ecpg->AddLibrary('ws2_32.lib'); + $pgregress_ecpg->AddDirResourceFile('src/interfaces/ecpg/test'); + $pgregress_ecpg->AddReference($libpgcommon, $libpgport); + + my $isolation_tester = + $solution->AddProject('isolationtester', 'exe', 'misc'); + $isolation_tester->AddFile('src/test/isolation/isolationtester.c'); + $isolation_tester->AddFile('src/test/isolation/specparse.y'); + $isolation_tester->AddFile('src/test/isolation/specscanner.l'); + $isolation_tester->AddFile('src/test/isolation/specparse.c'); + $isolation_tester->AddIncludeDir('src/test/isolation'); + $isolation_tester->AddIncludeDir('src/port'); + $isolation_tester->AddIncludeDir('src/test/regress'); + $isolation_tester->AddIncludeDir('src/interfaces/libpq'); + $isolation_tester->AddDefine('HOST_TUPLE="i686-pc-win32vc"'); + $isolation_tester->AddLibrary('ws2_32.lib'); + $isolation_tester->AddDirResourceFile('src/test/isolation'); + $isolation_tester->AddReference($libpq, $libpgcommon, $libpgport); + + my $pgregress_isolation = + $solution->AddProject('pg_isolation_regress', 'exe', 'misc'); + $pgregress_isolation->AddFile('src/test/isolation/isolation_main.c'); + $pgregress_isolation->AddFile('src/test/regress/pg_regress.c'); + $pgregress_isolation->AddIncludeDir('src/port'); + $pgregress_isolation->AddIncludeDir('src/test/regress'); + $pgregress_isolation->AddDefine('HOST_TUPLE="i686-pc-win32vc"'); + $pgregress_isolation->AddLibrary('ws2_32.lib'); + $pgregress_isolation->AddDirResourceFile('src/test/isolation'); + $pgregress_isolation->AddReference($libpgcommon, $libpgport); + + # src/bin + my $D; + opendir($D, 'src/bin') || croak "Could not opendir on src/bin!\n"; + while (my $d = readdir($D)) + { + next if ($d =~ /^\./); + next unless (-f "src/bin/$d/Makefile"); + next if (grep { /^$d$/ } @frontend_excludes); + AddSimpleFrontend($d); + } + + my $pgbasebackup = AddSimpleFrontend('pg_basebackup', 1); + $pgbasebackup->AddFile('src/bin/pg_basebackup/pg_basebackup.c'); + $pgbasebackup->AddLibrary('ws2_32.lib'); + + my $pgreceivewal = AddSimpleFrontend('pg_basebackup', 1); + $pgreceivewal->{name} = 'pg_receivewal'; + $pgreceivewal->AddFile('src/bin/pg_basebackup/pg_receivewal.c'); + $pgreceivewal->AddLibrary('ws2_32.lib'); + + my $pgrecvlogical = AddSimpleFrontend('pg_basebackup', 1); + $pgrecvlogical->{name} = 'pg_recvlogical'; + $pgrecvlogical->AddFile('src/bin/pg_basebackup/pg_recvlogical.c'); + $pgrecvlogical->AddLibrary('ws2_32.lib'); + + my $pgrewind = AddSimpleFrontend('pg_rewind', 1); + $pgrewind->{name} = 'pg_rewind'; + $pgrewind->AddFile('src/backend/access/transam/xlogreader.c'); + $pgrewind->AddLibrary('ws2_32.lib'); + $pgrewind->AddDefine('FRONTEND'); + + my $pgevent = $solution->AddProject('pgevent', 'dll', 'bin'); + $pgevent->AddFiles('src/bin/pgevent', 'pgevent.c', 'pgmsgevent.rc'); + $pgevent->AddResourceFile('src/bin/pgevent', 'Eventlog message formatter', + 'win32'); + $pgevent->RemoveFile('src/bin/pgevent/win32ver.rc'); + $pgevent->UseDef('src/bin/pgevent/pgevent.def'); + $pgevent->DisableLinkerWarnings('4104'); + + my $pgdump = AddSimpleFrontend('pg_dump', 1); + $pgdump->AddIncludeDir('src/backend'); + $pgdump->AddFile('src/bin/pg_dump/pg_dump.c'); + $pgdump->AddFile('src/bin/pg_dump/common.c'); + $pgdump->AddFile('src/bin/pg_dump/pg_dump_sort.c'); + $pgdump->AddLibrary('ws2_32.lib'); + + my $pgdumpall = AddSimpleFrontend('pg_dump', 1); + + # pg_dumpall doesn't use the files in the Makefile's $(OBJS), unlike + # pg_dump and pg_restore. + # So remove their sources from the object, keeping the other setup that + # AddSimpleFrontend() has done. + my @nodumpall = grep { m!src/bin/pg_dump/.*\.c$! } + keys %{ $pgdumpall->{files} }; + delete @{ $pgdumpall->{files} }{@nodumpall}; + $pgdumpall->{name} = 'pg_dumpall'; + $pgdumpall->AddIncludeDir('src/backend'); + $pgdumpall->AddFile('src/bin/pg_dump/pg_dumpall.c'); + $pgdumpall->AddFile('src/bin/pg_dump/dumputils.c'); + $pgdumpall->AddLibrary('ws2_32.lib'); + + my $pgrestore = AddSimpleFrontend('pg_dump', 1); + $pgrestore->{name} = 'pg_restore'; + $pgrestore->AddIncludeDir('src/backend'); + $pgrestore->AddFile('src/bin/pg_dump/pg_restore.c'); + $pgrestore->AddLibrary('ws2_32.lib'); + + my $zic = $solution->AddProject('zic', 'exe', 'utils'); + $zic->AddFiles('src/timezone', 'zic.c'); + $zic->AddDirResourceFile('src/timezone'); + $zic->AddReference($libpgcommon, $libpgport); + + if (!$solution->{options}->{xml}) + { + push @contrib_excludes, 'xml2'; + } + + if (!$solution->{options}->{openssl}) + { + push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback'; + } + + if (!$solution->{options}->{uuid}) + { + push @contrib_excludes, 'uuid-ossp'; + } + + # AddProject() does not recognize the constructs used to populate OBJS in + # the pgcrypto Makefile, so it will discover no files. + my $pgcrypto = + $solution->AddProject('pgcrypto', 'dll', 'crypto', 'contrib/pgcrypto'); + $pgcrypto->AddFiles( + 'contrib/pgcrypto', 'pgcrypto.c', + 'px.c', 'px-hmac.c', + 'px-crypt.c', 'crypt-gensalt.c', + 'crypt-blowfish.c', 'crypt-des.c', + 'crypt-md5.c', 'mbuf.c', + 'pgp.c', 'pgp-armor.c', + 'pgp-cfb.c', 'pgp-compress.c', + 'pgp-decrypt.c', 'pgp-encrypt.c', + 'pgp-info.c', 'pgp-mpi.c', + 'pgp-pubdec.c', 'pgp-pubenc.c', + 'pgp-pubkey.c', 'pgp-s2k.c', + 'pgp-pgsql.c'); + if ($solution->{options}->{openssl}) + { + $pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c', + 'pgp-mpi-openssl.c'); + } + else + { + $pgcrypto->AddFiles( + 'contrib/pgcrypto', 'internal.c', + 'internal-sha2.c', 'blf.c', + 'rijndael.c', 'pgp-mpi-internal.c', + 'imath.c'); + } + $pgcrypto->AddReference($postgres); + $pgcrypto->AddLibrary('ws2_32.lib'); + my $mf = Project::read_file('contrib/pgcrypto/Makefile'); + GenerateContribSqlFiles('pgcrypto', $mf); + + foreach my $subdir ('contrib', 'src/test/modules') + { + opendir($D, $subdir) || croak "Could not opendir on $subdir!\n"; + while (my $d = readdir($D)) + { + next if ($d =~ /^\./); + next unless (-f "$subdir/$d/Makefile"); + next if (grep { /^$d$/ } @contrib_excludes); + AddContrib($subdir, $d); + } + closedir($D); + } + + # Build Perl and Python modules after contrib/ modules to satisfy some + # dependencies with transform contrib modules, like hstore_plpython + # ltree_plpython and hstore_plperl. + if ($solution->{options}->{python}) + { + + # Attempt to get python version and location. + # Assume python.exe in specified dir. + my $pythonprog = "import sys;print(sys.prefix);" + . "print(str(sys.version_info[0])+str(sys.version_info[1]))"; + my $prefixcmd = + qq("$solution->{options}->{python}\\python" -c "$pythonprog"); + my $pyout = `$prefixcmd`; + die "Could not query for python version!\n" if $?; + my ($pyprefix, $pyver) = split(/\r?\n/, $pyout); + + # Sometimes (always?) if python is not present, the execution + # appears to work, but gives no data... + die "Failed to query python for version information\n" + if (!(defined($pyprefix) && defined($pyver))); + + my $pymajorver = substr($pyver, 0, 1); + my $plpython = $solution->AddProject('plpython' . $pymajorver, + 'dll', 'PLs', 'src/pl/plpython'); + $plpython->AddIncludeDir($pyprefix . '/include'); + $plpython->AddLibrary($pyprefix . "/Libs/python$pyver.lib"); + $plpython->AddReference($postgres); + + # Add transform modules dependent on plpython + my $hstore_plpython = AddTransformModule( + 'hstore_plpython' . $pymajorver, 'contrib/hstore_plpython', + 'plpython' . $pymajorver, 'src/pl/plpython', + 'hstore', 'contrib'); + $hstore_plpython->AddDefine( + 'PLPYTHON_LIBNAME="plpython' . $pymajorver . '"'); + my $jsonb_plpython = AddTransformModule( + 'jsonb_plpython' . $pymajorver, 'contrib/jsonb_plpython', + 'plpython' . $pymajorver, 'src/pl/plpython'); + $jsonb_plpython->AddDefine( + 'PLPYTHON_LIBNAME="plpython' . $pymajorver . '"'); + my $ltree_plpython = AddTransformModule( + 'ltree_plpython' . $pymajorver, 'contrib/ltree_plpython', + 'plpython' . $pymajorver, 'src/pl/plpython', + 'ltree', 'contrib'); + $ltree_plpython->AddDefine( + 'PLPYTHON_LIBNAME="plpython' . $pymajorver . '"'); + } + + if ($solution->{options}->{perl}) + { + my $plperlsrc = "src/pl/plperl/"; + my $plperl = + $solution->AddProject('plperl', 'dll', 'PLs', 'src/pl/plperl'); + $plperl->AddIncludeDir($solution->{options}->{perl} . '/lib/CORE'); + $plperl->AddReference($postgres); + + my $perl_path = $solution->{options}->{perl} . '\lib\CORE\*perl*'; + + # ActivePerl 5.16 provided perl516.lib; 5.18 provided libperl518.a + # Starting with ActivePerl 5.24, both perlnn.lib and libperlnn.a are provided. + # In this case, prefer .lib. + my @perl_libs = + reverse sort grep { /perl\d+\.lib$|libperl\d+\.a$/ } + glob($perl_path); + if (@perl_libs > 0) + { + $plperl->AddLibrary($perl_libs[0]); + } + else + { + die + "could not identify perl library version matching pattern $perl_path\n"; + } + + # Add defines from Perl's ccflags; see PGAC_CHECK_PERL_EMBED_CCFLAGS + my @perl_embed_ccflags; + foreach my $f (split(" ", $Config{ccflags})) + { + if ($f =~ /^-D[^_]/) + { + $f =~ s/\-D//; + push(@perl_embed_ccflags, $f); + } + } + + # hack to prevent duplicate definitions of uid_t/gid_t + push(@perl_embed_ccflags, 'PLPERL_HAVE_UID_GID'); + + # Windows offers several 32-bit ABIs. Perl is sensitive to + # sizeof(time_t), one of the ABI dimensions. To get 32-bit time_t, + # use "cl -D_USE_32BIT_TIME_T" or plain "gcc". For 64-bit time_t, use + # "gcc -D__MINGW_USE_VC2005_COMPAT" or plain "cl". Before MSVC 2005, + # plain "cl" chose 32-bit time_t. PostgreSQL doesn't support building + # with pre-MSVC-2005 compilers, but it does support linking to Perl + # built with such a compiler. MSVC-built Perl 5.13.4 and later report + # -D_USE_32BIT_TIME_T in $Config{ccflags} if applicable, but + # MinGW-built Perl never reports -D_USE_32BIT_TIME_T despite typically + # needing it. Ignore the $Config{ccflags} opinion about + # -D_USE_32BIT_TIME_T, and use a runtime test to deduce the ABI Perl + # expects. Specifically, test use of PL_modglobal, which maps to a + # PerlInterpreter field whose position depends on sizeof(time_t). + if ($solution->{platform} eq 'Win32') + { + my $source_file = 'conftest.c'; + my $obj = 'conftest.obj'; + my $exe = 'conftest.exe'; + my @conftest = ($source_file, $obj, $exe); + push @unlink_on_exit, @conftest; + unlink $source_file; + open my $o, '>', $source_file + || croak "Could not write to $source_file"; + print $o ' + /* compare to plperl.h */ + #define __inline__ __inline + #define PERL_NO_GET_CONTEXT + #include <EXTERN.h> + #include <perl.h> + + int + main(int argc, char **argv) + { + int dummy_argc = 1; + char *dummy_argv[1] = {""}; + char *dummy_env[1] = {NULL}; + static PerlInterpreter *interp; + + PERL_SYS_INIT3(&dummy_argc, (char ***) &dummy_argv, + (char ***) &dummy_env); + interp = perl_alloc(); + perl_construct(interp); + { + dTHX; + const char key[] = "dummy"; + + PL_exit_flags |= PERL_EXIT_DESTRUCT_END; + hv_store(PL_modglobal, key, sizeof(key) - 1, newSViv(1), 0); + return hv_fetch(PL_modglobal, key, sizeof(key) - 1, 0) == NULL; + } + } +'; + close $o; + + # Build $source_file with a given #define, and return a true value + # if a run of the resulting binary exits successfully. + my $try_define = sub { + my $define = shift; + + unlink $obj, $exe; + my @cmd = ( + 'cl', + '-I' . $solution->{options}->{perl} . '/lib/CORE', + (map { "-D$_" } @perl_embed_ccflags, $define || ()), + $source_file, + '/link', + $perl_libs[0]); + my $compile_output = `@cmd 2>&1`; + -f $exe || die "Failed to build Perl test:\n$compile_output"; + + { + + # Some builds exhibit runtime failure through Perl warning + # 'Can't spawn "conftest.exe"'; suppress that. + no warnings; + + no strict 'subs'; ## no critic (ProhibitNoStrict) + + # Disable error dialog boxes like we do in the postmaster. + # Here, we run code that triggers relevant errors. + use + if ($^O eq "MSWin32"), 'Win32API::File', + qw(SetErrorMode :SEM_); + my $oldmode = SetErrorMode( + SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + system(".\\$exe"); + SetErrorMode($oldmode); + } + + return !($? >> 8); + }; + + my $define_32bit_time = '_USE_32BIT_TIME_T'; + my $ok_now = $try_define->(undef); + my $ok_32bit = $try_define->($define_32bit_time); + unlink @conftest; + if (!$ok_now && !$ok_32bit) + { + + # Unsupported configuration. Since we used %Config from the + # Perl running the build scripts, this is expected if + # attempting to link with some other Perl. + die "Perl test fails with or without -D$define_32bit_time"; + } + elsif ($ok_now && $ok_32bit) + { + + # Resulting build may work, but it's especially important to + # verify with "vcregress plcheck". A refined test may avoid + # this outcome. + warn "Perl test passes with or without -D$define_32bit_time"; + } + elsif ($ok_32bit) + { + push(@perl_embed_ccflags, $define_32bit_time); + } # else $ok_now, hence no flag required + } + + print "CFLAGS recommended by Perl: $Config{ccflags}\n"; + print "CFLAGS to compile embedded Perl: ", + (join ' ', map { "-D$_" } @perl_embed_ccflags), "\n"; + foreach my $f (@perl_embed_ccflags) + { + $plperl->AddDefine($f); + } + + foreach my $xs ('SPI.xs', 'Util.xs') + { + (my $xsc = $xs) =~ s/\.xs/.c/; + if (Solution::IsNewer("$plperlsrc$xsc", "$plperlsrc$xs")) + { + my $xsubppdir = first { -e "$_/ExtUtils/xsubpp" } (@INC); + print "Building $plperlsrc$xsc...\n"; + system( $solution->{options}->{perl} + . '/bin/perl ' + . "$xsubppdir/ExtUtils/xsubpp -typemap " + . $solution->{options}->{perl} + . '/lib/ExtUtils/typemap ' + . "$plperlsrc$xs " + . ">$plperlsrc$xsc"); + if ((!(-f "$plperlsrc$xsc")) || -z "$plperlsrc$xsc") + { + unlink("$plperlsrc$xsc"); # if zero size + die "Failed to create $xsc.\n"; + } + } + } + if (Solution::IsNewer( + 'src/pl/plperl/perlchunks.h', + 'src/pl/plperl/plc_perlboot.pl') + || Solution::IsNewer( + 'src/pl/plperl/perlchunks.h', + 'src/pl/plperl/plc_trusted.pl')) + { + print 'Building src/pl/plperl/perlchunks.h ...' . "\n"; + my $basedir = getcwd; + chdir 'src/pl/plperl'; + system( $solution->{options}->{perl} + . '/bin/perl ' + . 'text2macro.pl ' + . '--strip="^(\#.*|\s*)$$" ' + . 'plc_perlboot.pl plc_trusted.pl ' + . '>perlchunks.h'); + chdir $basedir; + if ((!(-f 'src/pl/plperl/perlchunks.h')) + || -z 'src/pl/plperl/perlchunks.h') + { + unlink('src/pl/plperl/perlchunks.h'); # if zero size + die 'Failed to create perlchunks.h' . "\n"; + } + } + if (Solution::IsNewer( + 'src/pl/plperl/plperl_opmask.h', + 'src/pl/plperl/plperl_opmask.pl')) + { + print 'Building src/pl/plperl/plperl_opmask.h ...' . "\n"; + my $basedir = getcwd; + chdir 'src/pl/plperl'; + system( $solution->{options}->{perl} + . '/bin/perl ' + . 'plperl_opmask.pl ' + . 'plperl_opmask.h'); + chdir $basedir; + if ((!(-f 'src/pl/plperl/plperl_opmask.h')) + || -z 'src/pl/plperl/plperl_opmask.h') + { + unlink('src/pl/plperl/plperl_opmask.h'); # if zero size + die 'Failed to create plperl_opmask.h' . "\n"; + } + } + + # Add transform modules dependent on plperl + my $bool_plperl = AddTransformModule( + 'bool_plperl', 'contrib/bool_plperl', + 'plperl', 'src/pl/plperl'); + my $hstore_plperl = AddTransformModule( + 'hstore_plperl', 'contrib/hstore_plperl', + 'plperl', 'src/pl/plperl', + 'hstore', 'contrib'); + my $jsonb_plperl = AddTransformModule( + 'jsonb_plperl', 'contrib/jsonb_plperl', + 'plperl', 'src/pl/plperl'); + + foreach my $f (@perl_embed_ccflags) + { + $bool_plperl->AddDefine($f); + $hstore_plperl->AddDefine($f); + $jsonb_plperl->AddDefine($f); + } + } + + $mf = + Project::read_file('src/backend/utils/mb/conversion_procs/Makefile'); + $mf =~ s{\\\r?\n}{}g; + $mf =~ m{SUBDIRS\s*=\s*(.*)$}m + || die 'Could not match in conversion makefile' . "\n"; + foreach my $sub (split /\s+/, $1) + { + my $dir = 'src/backend/utils/mb/conversion_procs/' . $sub; + my $p = $solution->AddProject($sub, 'dll', 'conversion procs', $dir); + $p->AddFile("$dir/$sub.c"); # implicit source file + $p->AddReference($postgres); + } + + $mf = Project::read_file('src/bin/scripts/Makefile'); + $mf =~ s{\\\r?\n}{}g; + $mf =~ m{PROGRAMS\s*=\s*(.*)$}m + || die 'Could not match in bin/scripts/Makefile' . "\n"; + foreach my $prg (split /\s+/, $1) + { + my $proj = $solution->AddProject($prg, 'exe', 'bin'); + $mf =~ m{$prg\s*:\s*(.*)$}m + || die 'Could not find script define for $prg' . "\n"; + my @files = split /\s+/, $1; + foreach my $f (@files) + { + $f =~ s/\.o$/\.c/; + if ($f =~ /\.c$/) + { + $proj->AddFile('src/bin/scripts/' . $f); + } + } + $proj->AddIncludeDir('src/interfaces/libpq'); + $proj->AddReference($libpq, $libpgfeutils, $libpgcommon, $libpgport); + $proj->AddDirResourceFile('src/bin/scripts'); + $proj->AddLibrary('ws2_32.lib'); + } + + # Regression DLL and EXE + my $regress = $solution->AddProject('regress', 'dll', 'misc'); + $regress->AddFile('src/test/regress/regress.c'); + $regress->AddDirResourceFile('src/test/regress'); + $regress->AddReference($postgres); + + my $pgregress = $solution->AddProject('pg_regress', 'exe', 'misc'); + $pgregress->AddFile('src/test/regress/pg_regress.c'); + $pgregress->AddFile('src/test/regress/pg_regress_main.c'); + $pgregress->AddIncludeDir('src/port'); + $pgregress->AddDefine('HOST_TUPLE="i686-pc-win32vc"'); + $pgregress->AddLibrary('ws2_32.lib'); + $pgregress->AddDirResourceFile('src/test/regress'); + $pgregress->AddReference($libpgcommon, $libpgport); + + # fix up pg_waldump once it's been set up + # files symlinked on Unix are copied on windows + my $pg_waldump = AddSimpleFrontend('pg_waldump'); + $pg_waldump->AddDefine('FRONTEND'); + foreach my $xf (glob('src/backend/access/rmgrdesc/*desc.c')) + { + $pg_waldump->AddFile($xf); + } + $pg_waldump->AddFile('src/backend/access/transam/xlogreader.c'); + + $solution->Save(); + return $solution->{vcver}; +} + +##################### +# Utility functions # +##################### + +# Add a simple frontend project (exe) +sub AddSimpleFrontend +{ + my $n = shift; + my $uselibpq = shift; + + my $p = $solution->AddProject($n, 'exe', 'bin'); + $p->AddDir('src/bin/' . $n); + $p->AddReference($libpgfeutils, $libpgcommon, $libpgport); + if ($uselibpq) + { + $p->AddIncludeDir('src/interfaces/libpq'); + $p->AddReference($libpq); + } + + # Adjust module definition using frontend variables + AdjustFrontendProj($p); + + return $p; +} + +# Add a simple transform module +sub AddTransformModule +{ + my $n = shift; + my $n_src = shift; + my $pl_proj_name = shift; + my $pl_src = shift; + my $type_name = shift; + my $type_src = shift; + + my $type_proj = undef; + if ($type_name) + { + foreach my $proj (@{ $solution->{projects}->{'contrib'} }) + { + if ($proj->{name} eq $type_name) + { + $type_proj = $proj; + last; + } + } + die "could not find base module $type_name for transform module $n" + if (!defined($type_proj)); + } + + my $pl_proj = undef; + foreach my $proj (@{ $solution->{projects}->{'PLs'} }) + { + if ($proj->{name} eq $pl_proj_name) + { + $pl_proj = $proj; + last; + } + } + die "could not find PL $pl_proj_name for transform module $n" + if (!defined($pl_proj)); + + my $p = $solution->AddProject($n, 'dll', 'contrib', $n_src); + for my $file (glob("$n_src/*.c")) + { + $p->AddFile($file); + } + $p->AddReference($postgres); + + # Add PL dependencies + $p->AddIncludeDir($pl_src); + $p->AddReference($pl_proj); + $p->AddIncludeDir($pl_proj->{includes}); + foreach my $pl_lib (@{ $pl_proj->{libraries} }) + { + $p->AddLibrary($pl_lib); + } + + # Add base module dependencies + if ($type_proj) + { + $p->AddIncludeDir($type_src); + $p->AddIncludeDir($type_proj->{includes}); + foreach my $type_lib (@{ $type_proj->{libraries} }) + { + $p->AddLibrary($type_lib); + } + $p->AddReference($type_proj); + } + + return $p; +} + +# Add a simple contrib project +sub AddContrib +{ + my $subdir = shift; + my $n = shift; + my $mf = Project::read_file("$subdir/$n/Makefile"); + + if ($mf =~ /^MODULE_big\s*=\s*(.*)$/mg) + { + my $dn = $1; + my $proj = $solution->AddProject($dn, 'dll', 'contrib', "$subdir/$n"); + $proj->AddReference($postgres); + AdjustContribProj($proj); + } + elsif ($mf =~ /^MODULES\s*=\s*(.*)$/mg) + { + foreach my $mod (split /\s+/, $1) + { + my $proj = + $solution->AddProject($mod, 'dll', 'contrib', "$subdir/$n"); + my $filename = $mod . '.c'; + $proj->AddFile("$subdir/$n/$filename"); + $proj->AddReference($postgres); + AdjustContribProj($proj); + } + } + elsif ($mf =~ /^PROGRAM\s*=\s*(.*)$/mg) + { + my $proj = $solution->AddProject($1, 'exe', 'contrib', "$subdir/$n"); + AdjustContribProj($proj); + } + else + { + croak "Could not determine contrib module type for $n\n"; + } + + # Are there any output data files to build? + GenerateContribSqlFiles($n, $mf); + return; +} + +sub GenerateContribSqlFiles +{ + my $n = shift; + my $mf = shift; + $mf =~ s{\\\r?\n}{}g; + if ($mf =~ /^DATA_built\s*=\s*(.*)$/mg) + { + my $l = $1; + + # Strip out $(addsuffix) rules + if (index($l, '$(addsuffix ') >= 0) + { + my $pcount = 0; + my $i; + for ($i = index($l, '$(addsuffix ') + 12; $i < length($l); $i++) + { + $pcount++ if (substr($l, $i, 1) eq '('); + $pcount-- if (substr($l, $i, 1) eq ')'); + last if ($pcount < 0); + } + $l = + substr($l, 0, index($l, '$(addsuffix ')) . substr($l, $i + 1); + } + + foreach my $d (split /\s+/, $l) + { + my $in = "$d.in"; + my $out = "$d"; + + if (Solution::IsNewer("contrib/$n/$out", "contrib/$n/$in")) + { + print "Building $out from $in (contrib/$n)...\n"; + my $cont = Project::read_file("contrib/$n/$in"); + my $dn = $out; + $dn =~ s/\.sql$//; + $cont =~ s/MODULE_PATHNAME/\$libdir\/$dn/g; + my $o; + open($o, '>', "contrib/$n/$out") + || croak "Could not write to contrib/$n/$d"; + print $o $cont; + close($o); + } + } + } + return; +} + +sub AdjustContribProj +{ + my $proj = shift; + AdjustModule( + $proj, $contrib_defines, + \@contrib_uselibpq, \@contrib_uselibpgport, + \@contrib_uselibpgcommon, $contrib_extralibs, + $contrib_extrasource, $contrib_extraincludes); + return; +} + +sub AdjustFrontendProj +{ + my $proj = shift; + AdjustModule( + $proj, $frontend_defines, + \@frontend_uselibpq, \@frontend_uselibpgport, + \@frontend_uselibpgcommon, $frontend_extralibs, + $frontend_extrasource, $frontend_extraincludes); + return; +} + +sub AdjustModule +{ + my $proj = shift; + my $module_defines = shift; + my $module_uselibpq = shift; + my $module_uselibpgport = shift; + my $module_uselibpgcommon = shift; + my $module_extralibs = shift; + my $module_extrasource = shift; + my $module_extraincludes = shift; + my $n = $proj->{name}; + + if ($module_defines->{$n}) + { + foreach my $d ($module_defines->{$n}) + { + $proj->AddDefine($d); + } + } + if (grep { /^$n$/ } @{$module_uselibpq}) + { + $proj->AddIncludeDir('src\interfaces\libpq'); + $proj->AddReference($libpq); + } + if (grep { /^$n$/ } @{$module_uselibpgport}) + { + $proj->AddReference($libpgport); + } + if (grep { /^$n$/ } @{$module_uselibpgcommon}) + { + $proj->AddReference($libpgcommon); + } + if ($module_extralibs->{$n}) + { + foreach my $l (@{ $module_extralibs->{$n} }) + { + $proj->AddLibrary($l); + } + } + if ($module_extraincludes->{$n}) + { + foreach my $i (@{ $module_extraincludes->{$n} }) + { + $proj->AddIncludeDir($i); + } + } + if ($module_extrasource->{$n}) + { + foreach my $i (@{ $module_extrasource->{$n} }) + { + print "Files $i\n"; + $proj->AddFile($i); + } + } + return; +} + +END +{ + unlink @unlink_on_exit; +} + +1; diff --git a/src/tools/msvc/Project.pm b/src/tools/msvc/Project.pm new file mode 100644 index 0000000..2f1679a --- /dev/null +++ b/src/tools/msvc/Project.pm @@ -0,0 +1,448 @@ + +# Copyright (c) 2021, 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;4102;4090;4267', + disablelinkerwarnings => '', + platform => $solution->{platform}, + }; + + bless($self, $classname); + return $self; +} + +sub AddFile +{ + my ($self, $filename) = @_; + + $self->{files}->{$filename} = 1; + return; +} + +sub AddFiles +{ + my $self = shift; + my $dir = shift; + + while (my $f = shift) + { + $self->{files}->{ $dir . "/" . $f } = 1; + } + return; +} + +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->{files}{$newname} = 1; + return; + } + } + elsif ($file =~ m/($re)/) + { + delete $self->{files}{$file}; + $self->{files}{"$newname/$filename"} = 1; + 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) + { + 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 = '"' . $lib . """; + } + + push @{ $self->{libraries} }, $lib; + if ($dbgsuffix) + { + push @{ $self->{suffixlib} }, $lib; + } + return; +} + +sub AddIncludeDir +{ + my ($self, $inc) = @_; + + if ($self->{includes} ne '') + { + $self->{includes} .= ';'; + } + $self->{includes} .= $inc; + return; +} + +sub AddPrefixInclude +{ + my ($self, $inc) = @_; + + $self->{prefixincludes} = $inc . ';' . $self->{prefixincludes}; + return; +} + +sub AddDefine +{ + my ($self, $def) = @_; + + $def =~ s/"/""/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->{files}->{$f} = 1; + } + else + { + $self->{files}->{"$reldir/$f"} = 1; + } + } + $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) = @_; + + # If doing DLL and haven't specified a DEF file, do a full export of all symbols + # in the project. + if ($self->{type} eq "dll" && !$self->{def}) + { + $self->FullExportDLL($self->{name} . ".lib"); + } + + # 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; diff --git a/src/tools/msvc/README b/src/tools/msvc/README new file mode 100644 index 0000000..473d6f4 --- /dev/null +++ b/src/tools/msvc/README @@ -0,0 +1,99 @@ +src/tools/msvc/README + +MSVC build +========== + +This directory contains the tools required to build PostgreSQL using +Microsoft Visual Studio 2013 - 2022. This builds the whole backend, not just +the libpq frontend library. For more information, see the documentation +chapter "Installation on Windows" and the description below. + + +Notes about Visual Studio Express +--------------------------------- +To build PostgreSQL using Visual Studio Express, the Microsoft Windows SDK +has to be installed. Since this is not included in the product +originally, extra steps are needed to make it work. + +First, download and install a supported version of the Microsoft Windows SDK +from www.microsoft.com (v8.1a or greater). + +Locate the files vcprojectengine.dll.express.config and +vcprojectengine.dll.config in the vc\vcpackages directory of +the Visual C++ Express installation. In these files, add the paths +to the Platform SDK to the Include, Library and Path tags. Be sure +to add them to the beginning of the list. + +This should work for both GUI and commandline builds, but a restart +may be necessary. + +If you are using a recent version of the Microsoft Windows SDK that includes +the compilers and build tools you probably don't even need Visual Studio +Express to build PostgreSQL. + + +Structure of the build tools +---------------------------- +The tools for building PostgreSQL using Microsoft Visual Studio currently +consist of the following files: + +- Configuration files - +config_default.pl default configuration arguments + +A typical build environment has two more files, buildenv.pl and config.pl +that contain the user's build environment settings and configuration +arguments. + + +- User tools - +build.pl tool to build the binaries +clean.bat batch file for cleaning up generated files +install.pl tool to install the generated files +mkvcbuild.pl tool to generate the Visual Studio build files +vcregress.pl tool to run the regression tests + + +- Internal tools - +gendef.pl internal tool to generate .DEF files +pgbison.pl internal tool to process .y files using bison +pgflex.pl internal tool to process .l files using flex + +Many of those .pl files also have a corresponding .bat-wrapper that doesn't +contain any additional logic. + + +- Internal modules - +Install.pm module containing the install logic +Mkvcbuild.pm module containing the code to generate the Visual + Studio build (project/solution) files +MSBuildProject.pm module containing the code to generate MSBuild based + project files (Visual Studio 2013 or greater) +Project.pm module containing the common code to generate the + Visual Studio project files. Also provides the + common interface of all project file generators +Solution.pm module containing the code to generate the Visual + Studio solution files. +VSObjectFactory.pm factory module providing the code to create the + appropriate project/solution files for the current + environment + + +Description of the internals of the Visual Studio build process +--------------------------------------------------------------- +By typing 'build' the user starts the build.bat wrapper which simply passes +its arguments to build.pl. +In build.pl the user's buildenv.pl is used to set up the build environment +(i. e. path to bison and flex). In addition his config.pl file is merged into +config_default.pl to create the configuration arguments. +These configuration arguments are passed over to Mkvcbuild::mkvcbuild +(Mkvcbuild.pm) which creates the Visual Studio project and solution files. +It does this by using VSObjectFactory::CreateSolution to create an object +implementing the Solution interface (this could be either VS2013Solution, +VS2015Solution, VS2017Solution, VS2019Solution or VS2022Solution, all in +Solution.pm, depending on the user's build environment) and adding objects +implementing the corresponding Project interface (VC2013Project, +VC2015Project, VC2017Project, VC2019Project or VC2022Project from +MSBuildProject.pm) to it. When Solution::Save is called, the implementations +of Solution and Project save their content in the appropriate format. +The final step of starting the appropriate build program (msbuild) is +performed in build.pl again. diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm new file mode 100644 index 0000000..acd3b8d --- /dev/null +++ b/src/tools/msvc/Solution.pm @@ -0,0 +1,1359 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Solution; + +# +# Package that encapsulates a Visual C++ solution file generation +# +# src/tools/msvc/Solution.pm +# +use Carp; +use strict; +use warnings; +use VSObjectFactory; + +no warnings qw(redefine); ## no critic + +sub _new +{ + my $classname = shift; + my $options = shift; + my $self = { + projects => {}, + options => $options, + VisualStudioVersion => undef, + MinimumVisualStudioVersion => undef, + vcver => undef, + platform => undef, + }; + bless($self, $classname); + + $self->DeterminePlatform(); + + if ($options->{xslt} && !$options->{xml}) + { + die "XSLT requires XML\n"; + } + $options->{blocksize} = 8 + unless $options->{blocksize}; # undef or 0 means default + die "Bad blocksize $options->{blocksize}" + unless grep { $_ == $options->{blocksize} } (1, 2, 4, 8, 16, 32); + $options->{segsize} = 1 + unless $options->{segsize}; # undef or 0 means default + # only allow segsize 1 for now, as we can't do large files yet in windows + die "Bad segsize $options->{segsize}" + unless $options->{segsize} == 1; + $options->{wal_blocksize} = 8 + unless $options->{wal_blocksize}; # undef or 0 means default + die "Bad wal_blocksize $options->{wal_blocksize}" + unless grep { $_ == $options->{wal_blocksize} } + (1, 2, 4, 8, 16, 32, 64); + + return $self; +} + +sub GetAdditionalHeaders +{ + return ''; +} + +sub DeterminePlatform +{ + my $self = shift; + + if ($^O eq "MSWin32") + { + # Examine CL help output to determine if we are in 32 or 64-bit mode. + my $output = `cl /help 2>&1`; + $? >> 8 == 0 or die "cl command not found"; + $self->{platform} = + ($output =~ /^\/favor:<.+AMD64/m) ? 'x64' : 'Win32'; + } + else + { + $self->{platform} = 'FAKE'; + } + print "Detected hardware platform: $self->{platform}\n"; + return; +} + +# Return 1 if $oldfile is newer than $newfile, or if $newfile doesn't exist. +# Special case - if config.pl has changed, always return 1 +sub IsNewer +{ + my ($newfile, $oldfile) = @_; + -e $oldfile or warn "source file \"$oldfile\" does not exist"; + if ( $oldfile ne 'src/tools/msvc/config.pl' + && $oldfile ne 'src/tools/msvc/config_default.pl') + { + return 1 + if (-f 'src/tools/msvc/config.pl') + && IsNewer($newfile, 'src/tools/msvc/config.pl'); + return 1 + if (-f 'src/tools/msvc/config_default.pl') + && IsNewer($newfile, 'src/tools/msvc/config_default.pl'); + } + return 1 if (!(-e $newfile)); + my @nstat = stat($newfile); + my @ostat = stat($oldfile); + return 1 if ($nstat[9] < $ostat[9]); + return 0; +} + +# Copy a file, *not* preserving date. Only works for text files. +sub copyFile +{ + my ($src, $dest) = @_; + open(my $i, '<', $src) || croak "Could not open $src"; + open(my $o, '>', $dest) || croak "Could not open $dest"; + while (<$i>) + { + print $o $_; + } + close($i); + close($o); + return; +} + +# Fetch version of OpenSSL based on a parsing of the command shipped with +# the installer this build is linking to. This returns as result an array +# made of the three first digits of the OpenSSL version, which is enough +# to decide which options to apply depending on the version of OpenSSL +# linking with. +sub GetOpenSSLVersion +{ + my $self = shift; + + # Attempt to get OpenSSL version and location. This assumes that + # openssl.exe is in the specified directory. + # Quote the .exe name in case it has spaces + my $opensslcmd = + qq("$self->{options}->{openssl}\\bin\\openssl.exe" version 2>&1); + my $sslout = `$opensslcmd`; + + $? >> 8 == 0 + or croak + "Unable to determine OpenSSL version: The openssl.exe command wasn't found."; + + if ($sslout =~ /(\d+)\.(\d+)\.(\d+)(\D)/m) + { + return ($1, $2, $3); + } + + croak + "Unable to determine OpenSSL version: The openssl.exe version could not be determined."; +} + +sub GenerateFiles +{ + my $self = shift; + my $bits = $self->{platform} eq 'Win32' ? 32 : 64; + my $ac_init_found = 0; + my $package_name; + my $package_version; + my $package_bugreport; + my $package_url; + my ($majorver, $minorver); + my $ac_define_openssl_api_compat_found = 0; + my $openssl_api_compat; + + # Parse configure.ac to get version numbers + open(my $c, '<', "configure.ac") + || confess("Could not open configure.ac for reading\n"); + while (<$c>) + { + if (/^AC_INIT\(\[([^\]]+)\], \[([^\]]+)\], \[([^\]]+)\], \[([^\]]*)\], \[([^\]]+)\]/ + ) + { + $ac_init_found = 1; + + $package_name = $1; + $package_version = $2; + $package_bugreport = $3; + #$package_tarname = $4; + $package_url = $5; + + if ($package_version !~ /^(\d+)(?:\.(\d+))?/) + { + confess "Bad format of version: $package_version\n"; + } + $majorver = sprintf("%d", $1); + $minorver = sprintf("%d", $2 ? $2 : 0); + } + elsif (/\bAC_DEFINE\(OPENSSL_API_COMPAT, \[([0-9xL]+)\]/) + { + $ac_define_openssl_api_compat_found = 1; + $openssl_api_compat = $1; + } + } + close($c); + confess "Unable to parse configure.ac for all variables!" + unless $ac_init_found && $ac_define_openssl_api_compat_found; + + if (IsNewer("src/include/pg_config_os.h", "src/include/port/win32.h")) + { + print "Copying pg_config_os.h...\n"; + copyFile("src/include/port/win32.h", "src/include/pg_config_os.h"); + } + + print "Generating configuration headers...\n"; + my $extraver = $self->{options}->{extraver}; + $extraver = '' unless defined $extraver; + my $port = $self->{options}->{"--with-pgport"} || 5432; + + # Every symbol in pg_config.h.in must be accounted for here. Set + # to undef if the symbol should not be defined. + my %define = ( + ACCEPT_TYPE_ARG1 => 'unsigned int', + ACCEPT_TYPE_ARG2 => 'struct sockaddr *', + ACCEPT_TYPE_ARG3 => 'int', + ACCEPT_TYPE_RETURN => 'unsigned int PASCAL', + ALIGNOF_DOUBLE => 8, + ALIGNOF_INT => 4, + ALIGNOF_LONG => 4, + ALIGNOF_LONG_LONG_INT => 8, + ALIGNOF_PG_INT128_TYPE => undef, + ALIGNOF_SHORT => 2, + AC_APPLE_UNIVERSAL_BUILD => undef, + BLCKSZ => 1024 * $self->{options}->{blocksize}, + CONFIGURE_ARGS => '"' . $self->GetFakeConfigure() . '"', + DEF_PGPORT => $port, + DEF_PGPORT_STR => qq{"$port"}, + ENABLE_GSS => $self->{options}->{gss} ? 1 : undef, + ENABLE_NLS => $self->{options}->{nls} ? 1 : undef, + ENABLE_THREAD_SAFETY => 1, + GETTIMEOFDAY_1ARG => undef, + HAVE_APPEND_HISTORY => undef, + HAVE_ASN1_STRING_GET0_DATA => undef, + HAVE_ATOMICS => 1, + HAVE_ATOMIC_H => undef, + HAVE_BACKTRACE_SYMBOLS => undef, + HAVE_BIO_GET_DATA => undef, + HAVE_BIO_METH_NEW => undef, + HAVE_CLOCK_GETTIME => undef, + HAVE_COMPUTED_GOTO => undef, + HAVE_COPYFILE => undef, + HAVE_COPYFILE_H => undef, + HAVE_CRTDEFS_H => undef, + HAVE_CRYPTO_LOCK => undef, + HAVE_DECL_FDATASYNC => 0, + HAVE_DECL_F_FULLFSYNC => 0, + HAVE_DECL_LLVMCREATEGDBREGISTRATIONLISTENER => 0, + HAVE_DECL_LLVMCREATEPERFJITEVENTLISTENER => 0, + HAVE_DECL_LLVMGETHOSTCPUNAME => 0, + HAVE_DECL_LLVMGETHOSTCPUFEATURES => 0, + HAVE_DECL_LLVMORCGETSYMBOLADDRESSIN => 0, + HAVE_DECL_POSIX_FADVISE => 0, + HAVE_DECL_PREADV => 0, + HAVE_DECL_PWRITEV => 0, + HAVE_DECL_RTLD_GLOBAL => 0, + HAVE_DECL_RTLD_NOW => 0, + HAVE_DECL_STRLCAT => 0, + HAVE_DECL_STRLCPY => 0, + HAVE_DECL_STRNLEN => 1, + HAVE_DECL_STRTOLL => 1, + HAVE_DECL_STRTOULL => 1, + HAVE_DLOPEN => undef, + HAVE_EDITLINE_HISTORY_H => undef, + HAVE_EDITLINE_READLINE_H => undef, + HAVE_EXECINFO_H => undef, + HAVE_EXPLICIT_BZERO => undef, + HAVE_FDATASYNC => undef, + HAVE_FLS => undef, + HAVE_FSEEKO => 1, + HAVE_FUNCNAME__FUNC => undef, + HAVE_FUNCNAME__FUNCTION => 1, + HAVE_GCC__ATOMIC_INT32_CAS => undef, + HAVE_GCC__ATOMIC_INT64_CAS => undef, + HAVE_GCC__SYNC_CHAR_TAS => undef, + HAVE_GCC__SYNC_INT32_CAS => undef, + HAVE_GCC__SYNC_INT32_TAS => undef, + HAVE_GCC__SYNC_INT64_CAS => undef, + HAVE_GETADDRINFO => undef, + HAVE_GETHOSTBYNAME_R => undef, + HAVE_GETIFADDRS => undef, + HAVE_GETOPT => undef, + HAVE_GETOPT_H => undef, + HAVE_GETOPT_LONG => undef, + HAVE_GETPEEREID => undef, + HAVE_GETPEERUCRED => undef, + HAVE_GETPWUID_R => undef, + HAVE_GETRLIMIT => undef, + HAVE_GETRUSAGE => undef, + HAVE_GETTIMEOFDAY => undef, + HAVE_GSSAPI_GSSAPI_H => undef, + HAVE_GSSAPI_H => undef, + HAVE_HMAC_CTX_FREE => undef, + HAVE_HMAC_CTX_NEW => undef, + HAVE_HISTORY_H => undef, + HAVE_HISTORY_TRUNCATE_FILE => undef, + HAVE_IFADDRS_H => undef, + HAVE_INET_ATON => undef, + HAVE_INT_TIMEZONE => 1, + HAVE_INT64 => undef, + HAVE_INT8 => undef, + HAVE_INTTYPES_H => undef, + HAVE_INT_OPTERR => undef, + HAVE_INT_OPTRESET => undef, + HAVE_IPV6 => 1, + HAVE_I_CONSTRAINT__BUILTIN_CONSTANT_P => undef, + HAVE_KQUEUE => undef, + HAVE_LANGINFO_H => undef, + HAVE_LDAP_H => undef, + HAVE_LDAP_INITIALIZE => undef, + HAVE_LIBCRYPTO => undef, + HAVE_LIBLDAP => undef, + HAVE_LIBLZ4 => undef, + HAVE_LIBM => undef, + HAVE_LIBPAM => undef, + HAVE_LIBREADLINE => undef, + HAVE_LIBSELINUX => undef, + HAVE_LIBSSL => undef, + HAVE_LIBWLDAP32 => undef, + HAVE_LIBXML2 => undef, + HAVE_LIBXSLT => undef, + HAVE_LIBZ => $self->{options}->{zlib} ? 1 : undef, + HAVE_LINK => undef, + HAVE_LOCALE_T => 1, + HAVE_LONG_INT_64 => undef, + HAVE_LONG_LONG_INT_64 => 1, + HAVE_LZ4_H => undef, + HAVE_MBARRIER_H => undef, + HAVE_MBSTOWCS_L => 1, + HAVE_MEMORY_H => 1, + HAVE_MEMSET_S => undef, + HAVE_MINIDUMP_TYPE => 1, + HAVE_MKDTEMP => undef, + HAVE_NETINET_TCP_H => undef, + HAVE_NET_IF_H => undef, + HAVE_OPENSSL_INIT_SSL => undef, + HAVE_OSSP_UUID_H => undef, + HAVE_PAM_PAM_APPL_H => undef, + HAVE_POLL => undef, + HAVE_POLL_H => undef, + HAVE_POSIX_FADVISE => undef, + HAVE_POSIX_FALLOCATE => undef, + HAVE_PPC_LWARX_MUTEX_HINT => undef, + HAVE_PPOLL => undef, + HAVE_PREAD => undef, + HAVE_PSTAT => undef, + HAVE_PS_STRINGS => undef, + HAVE_PTHREAD => undef, + HAVE_PTHREAD_BARRIER_WAIT => undef, + HAVE_PTHREAD_IS_THREADED_NP => undef, + HAVE_PTHREAD_PRIO_INHERIT => undef, + HAVE_PWRITE => undef, + HAVE_RANDOM => undef, + HAVE_READLINE_H => undef, + HAVE_READLINE_HISTORY_H => undef, + HAVE_READLINE_READLINE_H => undef, + HAVE_READLINK => undef, + HAVE_READV => undef, + HAVE_RL_COMPLETION_APPEND_CHARACTER => undef, + HAVE_RL_COMPLETION_MATCHES => undef, + HAVE_RL_COMPLETION_SUPPRESS_QUOTE => undef, + HAVE_RL_FILENAME_COMPLETION_FUNCTION => undef, + HAVE_RL_FILENAME_QUOTE_CHARACTERS => undef, + HAVE_RL_FILENAME_QUOTING_FUNCTION => undef, + HAVE_RL_RESET_SCREEN_SIZE => undef, + HAVE_SECURITY_PAM_APPL_H => undef, + HAVE_SETENV => undef, + HAVE_SETPROCTITLE => undef, + HAVE_SETPROCTITLE_FAST => undef, + HAVE_SETSID => undef, + HAVE_SHM_OPEN => undef, + HAVE_SPINLOCKS => 1, + HAVE_SRANDOM => undef, + HAVE_STDBOOL_H => 1, + HAVE_STDINT_H => 1, + HAVE_STDLIB_H => 1, + HAVE_STRCHRNUL => undef, + HAVE_STRERROR_R => undef, + HAVE_STRINGS_H => undef, + HAVE_STRING_H => 1, + HAVE_STRLCAT => undef, + HAVE_STRLCPY => undef, + HAVE_STRNLEN => 1, + HAVE_STRSIGNAL => undef, + HAVE_STRTOF => 1, + HAVE_STRTOLL => 1, + HAVE_STRTOQ => undef, + HAVE_STRTOULL => 1, + HAVE_STRTOUQ => undef, + HAVE_STRUCT_ADDRINFO => 1, + HAVE_STRUCT_CMSGCRED => undef, + HAVE_STRUCT_OPTION => undef, + HAVE_STRUCT_SOCKADDR_SA_LEN => undef, + HAVE_STRUCT_SOCKADDR_STORAGE => 1, + HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY => 1, + HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN => undef, + HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY => undef, + HAVE_STRUCT_SOCKADDR_STORAGE___SS_LEN => undef, + HAVE_STRUCT_SOCKADDR_UN => undef, + HAVE_STRUCT_TM_TM_ZONE => undef, + HAVE_SYNC_FILE_RANGE => undef, + HAVE_SYMLINK => 1, + HAVE_SYNCFS => undef, + HAVE_SYSLOG => undef, + HAVE_SYS_EPOLL_H => undef, + HAVE_SYS_EVENT_H => undef, + HAVE_SYS_IPC_H => undef, + HAVE_SYS_PRCTL_H => undef, + HAVE_SYS_PROCCTL_H => undef, + HAVE_SYS_PSTAT_H => undef, + HAVE_SYS_RESOURCE_H => undef, + HAVE_SYS_SELECT_H => undef, + HAVE_SYS_SEM_H => undef, + HAVE_SYS_SHM_H => undef, + HAVE_SYS_SIGNALFD_H => undef, + HAVE_SYS_SOCKIO_H => undef, + HAVE_SYS_STAT_H => 1, + HAVE_SYS_TAS_H => undef, + HAVE_SYS_TYPES_H => 1, + HAVE_SYS_UCRED_H => undef, + HAVE_SYS_UIO_H => undef, + HAVE_SYS_UN_H => undef, + HAVE_TERMIOS_H => undef, + HAVE_TYPEOF => undef, + HAVE_UCRED_H => undef, + HAVE_UINT64 => undef, + HAVE_UINT8 => undef, + HAVE_UNION_SEMUN => undef, + HAVE_UNISTD_H => 1, + HAVE_UNSETENV => undef, + HAVE_USELOCALE => undef, + HAVE_UUID_BSD => undef, + HAVE_UUID_E2FS => undef, + HAVE_UUID_OSSP => undef, + HAVE_UUID_H => undef, + HAVE_UUID_UUID_H => undef, + HAVE_WINLDAP_H => undef, + HAVE_WCSTOMBS_L => 1, + HAVE_WCTYPE_H => 1, + HAVE_WRITEV => undef, + HAVE_X509_GET_SIGNATURE_NID => 1, + HAVE_X86_64_POPCNTQ => undef, + HAVE__BOOL => undef, + HAVE__BUILTIN_BSWAP16 => undef, + HAVE__BUILTIN_BSWAP32 => undef, + HAVE__BUILTIN_BSWAP64 => undef, + HAVE__BUILTIN_CLZ => undef, + HAVE__BUILTIN_CONSTANT_P => undef, + HAVE__BUILTIN_CTZ => undef, + HAVE__BUILTIN_FRAME_ADDRESS => undef, + HAVE__BUILTIN_OP_OVERFLOW => undef, + HAVE__BUILTIN_POPCOUNT => undef, + HAVE__BUILTIN_TYPES_COMPATIBLE_P => undef, + HAVE__BUILTIN_UNREACHABLE => undef, + HAVE__CONFIGTHREADLOCALE => 1, + HAVE__CPUID => 1, + HAVE__GET_CPUID => undef, + HAVE__STATIC_ASSERT => undef, + HAVE___STRTOLL => undef, + HAVE___STRTOULL => undef, + INT64_MODIFIER => qq{"ll"}, + LOCALE_T_IN_XLOCALE => undef, + MAXIMUM_ALIGNOF => 8, + MEMSET_LOOP_LIMIT => 1024, + OPENSSL_API_COMPAT => $openssl_api_compat, + PACKAGE_BUGREPORT => qq{"$package_bugreport"}, + PACKAGE_NAME => qq{"$package_name"}, + PACKAGE_STRING => qq{"$package_name $package_version"}, + PACKAGE_TARNAME => lc qq{"$package_name"}, + PACKAGE_URL => qq{"$package_url"}, + PACKAGE_VERSION => qq{"$package_version"}, + PG_INT128_TYPE => undef, + PG_INT64_TYPE => 'long long int', + PG_KRB_SRVNAM => qq{"postgres"}, + PG_MAJORVERSION => qq{"$majorver"}, + PG_MAJORVERSION_NUM => $majorver, + PG_MINORVERSION_NUM => $minorver, + PG_PRINTF_ATTRIBUTE => undef, + PG_USE_STDBOOL => 1, + PG_VERSION => qq{"$package_version$extraver"}, + PG_VERSION_NUM => sprintf("%d%04d", $majorver, $minorver), + PG_VERSION_STR => + qq{"PostgreSQL $package_version$extraver, compiled by Visual C++ build " CppAsString2(_MSC_VER) ", $bits-bit"}, + PROFILE_PID_DIR => undef, + PTHREAD_CREATE_JOINABLE => undef, + RELSEG_SIZE => (1024 / $self->{options}->{blocksize}) * + $self->{options}->{segsize} * 1024, + SIZEOF_BOOL => 1, + SIZEOF_LONG => 4, + SIZEOF_OFF_T => undef, + SIZEOF_SIZE_T => $bits / 8, + SIZEOF_VOID_P => $bits / 8, + STDC_HEADERS => 1, + STRERROR_R_INT => undef, + USE_ARMV8_CRC32C => undef, + USE_ARMV8_CRC32C_WITH_RUNTIME_CHECK => undef, + USE_ASSERT_CHECKING => $self->{options}->{asserts} ? 1 : undef, + USE_BONJOUR => undef, + USE_BSD_AUTH => undef, + USE_ICU => $self->{options}->{icu} ? 1 : undef, + USE_LIBXML => undef, + USE_LIBXSLT => undef, + USE_LZ4 => undef, + USE_LDAP => $self->{options}->{ldap} ? 1 : undef, + USE_LLVM => undef, + USE_NAMED_POSIX_SEMAPHORES => undef, + USE_OPENSSL => undef, + USE_PAM => undef, + USE_SLICING_BY_8_CRC32C => undef, + USE_SSE42_CRC32C => undef, + USE_SSE42_CRC32C_WITH_RUNTIME_CHECK => 1, + USE_SYSTEMD => undef, + USE_SYSV_SEMAPHORES => undef, + USE_SYSV_SHARED_MEMORY => undef, + USE_UNNAMED_POSIX_SEMAPHORES => undef, + USE_WIN32_SEMAPHORES => 1, + USE_WIN32_SHARED_MEMORY => 1, + WCSTOMBS_L_IN_XLOCALE => undef, + WORDS_BIGENDIAN => undef, + XLOG_BLCKSZ => 1024 * $self->{options}->{wal_blocksize}, + _FILE_OFFSET_BITS => undef, + _LARGEFILE_SOURCE => undef, + _LARGE_FILES => undef, + inline => '__inline', + pg_restrict => '__restrict', + # not defined, because it'd conflict with __declspec(restrict) + restrict => undef, + typeof => undef,); + + if ($self->{options}->{uuid}) + { + $define{HAVE_UUID_OSSP} = 1; + $define{HAVE_UUID_H} = 1; + } + if ($self->{options}->{xml}) + { + $define{HAVE_LIBXML2} = 1; + $define{USE_LIBXML} = 1; + } + if ($self->{options}->{xslt}) + { + $define{HAVE_LIBXSLT} = 1; + $define{USE_LIBXSLT} = 1; + } + if ($self->{options}->{lz4}) + { + $define{HAVE_LIBLZ4} = 1; + $define{HAVE_LZ4_H} = 1; + $define{USE_LZ4} = 1; + } + if ($self->{options}->{openssl}) + { + $define{USE_OPENSSL} = 1; + + my ($digit1, $digit2, $digit3) = $self->GetOpenSSLVersion(); + + # More symbols are needed with OpenSSL 1.1.0 and above. + if ( ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0') + || ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0')) + { + $define{HAVE_ASN1_STRING_GET0_DATA} = 1; + $define{HAVE_BIO_GET_DATA} = 1; + $define{HAVE_BIO_METH_NEW} = 1; + $define{HAVE_HMAC_CTX_FREE} = 1; + $define{HAVE_HMAC_CTX_NEW} = 1; + $define{HAVE_OPENSSL_INIT_SSL} = 1; + } + } + + $self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1); + $self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0); + $self->GenerateConfigHeader('src/interfaces/ecpg/include/ecpg_config.h', + \%define, 0); + + $self->GenerateDefFile( + "src/interfaces/libpq/libpqdll.def", + "src/interfaces/libpq/exports.txt", + "LIBPQ"); + $self->GenerateDefFile( + "src/interfaces/ecpg/ecpglib/ecpglib.def", + "src/interfaces/ecpg/ecpglib/exports.txt", + "LIBECPG"); + $self->GenerateDefFile( + "src/interfaces/ecpg/compatlib/compatlib.def", + "src/interfaces/ecpg/compatlib/exports.txt", + "LIBECPG_COMPAT"); + $self->GenerateDefFile( + "src/interfaces/ecpg/pgtypeslib/pgtypeslib.def", + "src/interfaces/ecpg/pgtypeslib/exports.txt", + "LIBPGTYPES"); + + chdir('src/backend/utils'); + my $pg_proc_dat = '../../../src/include/catalog/pg_proc.dat'; + if ( IsNewer('fmgr-stamp', 'Gen_fmgrtab.pl') + || IsNewer('fmgr-stamp', '../catalog/Catalog.pm') + || IsNewer('fmgr-stamp', $pg_proc_dat) + || IsNewer('fmgr-stamp', '../../../src/include/access/transam.h')) + { + system( + "perl -I ../catalog Gen_fmgrtab.pl --include-path ../../../src/include/ $pg_proc_dat" + ); + open(my $f, '>', 'fmgr-stamp') + || confess "Could not touch fmgr-stamp"; + close($f); + } + chdir('../../..'); + + if (IsNewer( + 'src/include/utils/fmgroids.h', + 'src/backend/utils/fmgroids.h')) + { + copyFile('src/backend/utils/fmgroids.h', + 'src/include/utils/fmgroids.h'); + } + + if (IsNewer( + 'src/include/utils/fmgrprotos.h', + 'src/backend/utils/fmgrprotos.h')) + { + copyFile( + 'src/backend/utils/fmgrprotos.h', + 'src/include/utils/fmgrprotos.h'); + } + + if (IsNewer( + 'src/include/storage/lwlocknames.h', + 'src/backend/storage/lmgr/lwlocknames.txt')) + { + print "Generating lwlocknames.c and lwlocknames.h...\n"; + chdir('src/backend/storage/lmgr'); + system('perl generate-lwlocknames.pl lwlocknames.txt'); + chdir('../../../..'); + } + if (IsNewer( + 'src/include/storage/lwlocknames.h', + 'src/backend/storage/lmgr/lwlocknames.h')) + { + copyFile( + 'src/backend/storage/lmgr/lwlocknames.h', + 'src/include/storage/lwlocknames.h'); + } + + if (IsNewer('src/include/utils/probes.h', 'src/backend/utils/probes.d')) + { + print "Generating probes.h...\n"; + system( + 'perl src/backend/utils/Gen_dummy_probes.pl src/backend/utils/probes.d > src/include/utils/probes.h' + ); + } + + if ($self->{options}->{python} + && IsNewer( + 'src/pl/plpython/spiexceptions.h', + 'src/backend/utils/errcodes.txt')) + { + print "Generating spiexceptions.h...\n"; + system( + 'perl src/pl/plpython/generate-spiexceptions.pl src/backend/utils/errcodes.txt > src/pl/plpython/spiexceptions.h' + ); + } + + if (IsNewer( + 'src/include/utils/errcodes.h', + 'src/backend/utils/errcodes.txt')) + { + print "Generating errcodes.h...\n"; + system( + 'perl src/backend/utils/generate-errcodes.pl src/backend/utils/errcodes.txt > src/backend/utils/errcodes.h' + ); + copyFile('src/backend/utils/errcodes.h', + 'src/include/utils/errcodes.h'); + } + + if (IsNewer( + 'src/pl/plpgsql/src/plerrcodes.h', + 'src/backend/utils/errcodes.txt')) + { + print "Generating plerrcodes.h...\n"; + system( + 'perl src/pl/plpgsql/src/generate-plerrcodes.pl src/backend/utils/errcodes.txt > src/pl/plpgsql/src/plerrcodes.h' + ); + } + + if ($self->{options}->{tcl} + && IsNewer( + 'src/pl/tcl/pltclerrcodes.h', 'src/backend/utils/errcodes.txt')) + { + print "Generating pltclerrcodes.h...\n"; + system( + 'perl src/pl/tcl/generate-pltclerrcodes.pl src/backend/utils/errcodes.txt > src/pl/tcl/pltclerrcodes.h' + ); + } + + if (IsNewer('src/bin/psql/sql_help.h', 'src/bin/psql/create_help.pl')) + { + print "Generating sql_help.h...\n"; + chdir('src/bin/psql'); + system("perl create_help.pl ../../../doc/src/sgml/ref sql_help"); + chdir('../../..'); + } + + if (IsNewer('src/common/kwlist_d.h', 'src/include/parser/kwlist.h')) + { + print "Generating kwlist_d.h...\n"; + system( + 'perl -I src/tools src/tools/gen_keywordlist.pl --extern -o src/common src/include/parser/kwlist.h' + ); + } + + if (IsNewer( + 'src/pl/plpgsql/src/pl_reserved_kwlist_d.h', + 'src/pl/plpgsql/src/pl_reserved_kwlist.h') + || IsNewer( + 'src/pl/plpgsql/src/pl_unreserved_kwlist_d.h', + 'src/pl/plpgsql/src/pl_unreserved_kwlist.h')) + { + print + "Generating pl_reserved_kwlist_d.h and pl_unreserved_kwlist_d.h...\n"; + chdir('src/pl/plpgsql/src'); + system( + 'perl -I ../../../tools ../../../tools/gen_keywordlist.pl --varname ReservedPLKeywords pl_reserved_kwlist.h' + ); + system( + 'perl -I ../../../tools ../../../tools/gen_keywordlist.pl --varname UnreservedPLKeywords pl_unreserved_kwlist.h' + ); + chdir('../../../..'); + } + + if (IsNewer( + 'src/interfaces/ecpg/preproc/c_kwlist_d.h', + 'src/interfaces/ecpg/preproc/c_kwlist.h') + || IsNewer( + 'src/interfaces/ecpg/preproc/ecpg_kwlist_d.h', + 'src/interfaces/ecpg/preproc/ecpg_kwlist.h')) + { + print "Generating c_kwlist_d.h and ecpg_kwlist_d.h...\n"; + chdir('src/interfaces/ecpg/preproc'); + system( + 'perl -I ../../../tools ../../../tools/gen_keywordlist.pl --varname ScanCKeywords --no-case-fold c_kwlist.h' + ); + system( + 'perl -I ../../../tools ../../../tools/gen_keywordlist.pl --varname ScanECPGKeywords ecpg_kwlist.h' + ); + chdir('../../../..'); + } + + if (IsNewer( + 'src/interfaces/ecpg/preproc/preproc.y', + 'src/backend/parser/gram.y')) + { + print "Generating preproc.y...\n"; + chdir('src/interfaces/ecpg/preproc'); + system('perl parse.pl < ../../../backend/parser/gram.y > preproc.y'); + chdir('../../../..'); + } + + unless (-f "src/port/pg_config_paths.h") + { + print "Generating pg_config_paths.h...\n"; + open(my $o, '>', 'src/port/pg_config_paths.h') + || confess "Could not open pg_config_paths.h"; + print $o <<EOF; +#define PGBINDIR "/bin" +#define PGSHAREDIR "/share" +#define SYSCONFDIR "/etc" +#define INCLUDEDIR "/include" +#define PKGINCLUDEDIR "/include" +#define INCLUDEDIRSERVER "/include/server" +#define LIBDIR "/lib" +#define PKGLIBDIR "/lib" +#define LOCALEDIR "/share/locale" +#define DOCDIR "/doc" +#define HTMLDIR "/doc" +#define MANDIR "/man" +EOF + close($o); + } + + my $mf = Project::read_file('src/backend/catalog/Makefile'); + $mf =~ s{\\\r?\n}{}g; + $mf =~ /^CATALOG_HEADERS\s*:?=(.*)$/gm + || croak "Could not find CATALOG_HEADERS in Makefile\n"; + my @bki_srcs = split /\s+/, $1; + $mf =~ /^POSTGRES_BKI_DATA\s*:?=[^,]+,(.*)\)$/gm + || croak "Could not find POSTGRES_BKI_DATA in Makefile\n"; + my @bki_data = split /\s+/, $1; + + my $need_genbki = 0; + foreach my $bki (@bki_srcs, @bki_data) + { + next if $bki eq ""; + if (IsNewer( + 'src/backend/catalog/bki-stamp', + "src/include/catalog/$bki")) + { + $need_genbki = 1; + last; + } + } + $need_genbki = 1 + if IsNewer('src/backend/catalog/bki-stamp', + 'src/backend/catalog/genbki.pl'); + $need_genbki = 1 + if IsNewer('src/backend/catalog/bki-stamp', + 'src/backend/catalog/Catalog.pm'); + if ($need_genbki) + { + chdir('src/backend/catalog'); + my $bki_srcs = join(' ../../../src/include/catalog/', @bki_srcs); + system( + "perl genbki.pl --include-path ../../../src/include/ --set-version=$majorver $bki_srcs" + ); + open(my $f, '>', 'bki-stamp') + || confess "Could not touch bki-stamp"; + close($f); + chdir('../../..'); + } + + if (IsNewer( + 'src/include/catalog/header-stamp', + 'src/backend/catalog/bki-stamp')) + { + # Copy generated headers to include directory. + opendir(my $dh, 'src/backend/catalog/') + || die "Can't opendir src/backend/catalog/ $!"; + my @def_headers = grep { /pg_\w+_d\.h$/ } readdir($dh); + closedir $dh; + foreach my $def_header (@def_headers) + { + copyFile( + "src/backend/catalog/$def_header", + "src/include/catalog/$def_header"); + } + copyFile( + 'src/backend/catalog/schemapg.h', + 'src/include/catalog/schemapg.h'); + copyFile( + 'src/backend/catalog/system_fk_info.h', + 'src/include/catalog/system_fk_info.h'); + open(my $chs, '>', 'src/include/catalog/header-stamp') + || confess "Could not touch header-stamp"; + close($chs); + } + + open(my $o, '>', "doc/src/sgml/version.sgml") + || croak "Could not write to version.sgml\n"; + print $o <<EOF; +<!ENTITY version "$package_version"> +<!ENTITY majorversion "$majorver"> +EOF + close($o); + return; +} + +# Read lines from input file and substitute symbols using the same +# logic that config.status uses. There should be one call of this for +# each AC_CONFIG_HEADERS call in configure.ac. +# +# If the "required" argument is true, we also keep track which of our +# defines have been found and error out if any are left unused at the +# end. That way we avoid accumulating defines in this file that are +# no longer used by configure. +sub GenerateConfigHeader +{ + my ($self, $config_header, $defines, $required) = @_; + + my $config_header_in = $config_header . '.in'; + + if ( IsNewer($config_header, $config_header_in) + || IsNewer($config_header, __FILE__)) + { + my %defines_copy = %$defines; + + open(my $i, '<', $config_header_in) + || confess "Could not open $config_header_in\n"; + open(my $o, '>', $config_header) + || confess "Could not write to $config_header\n"; + + print $o + "/* $config_header. Generated from $config_header_in by src/tools/msvc/Solution.pm. */\n"; + + while (<$i>) + { + if (m/^#(\s*)undef\s+(\w+)/) + { + my $ws = $1; + my $macro = $2; + if (exists $defines->{$macro}) + { + if (defined $defines->{$macro}) + { + print $o "#${ws}define $macro ", $defines->{$macro}, + "\n"; + } + else + { + print $o "/* #${ws}undef $macro */\n"; + } + delete $defines_copy{$macro}; + } + else + { + croak + "undefined symbol: $macro at $config_header line $."; + } + } + else + { + print $o $_; + } + } + close($o); + close($i); + + if ($required && scalar(keys %defines_copy) > 0) + { + croak "unused defines: " . join(' ', keys %defines_copy); + } + } +} + +sub GenerateDefFile +{ + my ($self, $deffile, $txtfile, $libname) = @_; + + if (IsNewer($deffile, $txtfile)) + { + print "Generating $deffile...\n"; + open(my $if, '<', $txtfile) || confess("Could not open $txtfile\n"); + open(my $of, '>', $deffile) || confess("Could not open $deffile\n"); + print $of "LIBRARY $libname\nEXPORTS\n"; + while (<$if>) + { + next if (/^#/); + next if (/^\s*$/); + my ($f, $o) = split; + print $of " $f @ $o\n"; + } + close($of); + close($if); + } + return; +} + +sub AddProject +{ + my ($self, $name, $type, $folder, $initialdir) = @_; + + my $proj = + VSObjectFactory::CreateProject($self->{vcver}, $name, $type, $self); + push @{ $self->{projects}->{$folder} }, $proj; + $proj->AddDir($initialdir) if ($initialdir); + if ($self->{options}->{zlib}) + { + $proj->AddIncludeDir($self->{options}->{zlib} . '\include'); + $proj->AddLibrary($self->{options}->{zlib} . '\lib\zdll.lib'); + } + if ($self->{options}->{openssl}) + { + $proj->AddIncludeDir($self->{options}->{openssl} . '\include'); + my ($digit1, $digit2, $digit3) = $self->GetOpenSSLVersion(); + + # Starting at version 1.1.0 the OpenSSL installers have + # changed their library names from: + # - libeay to libcrypto + # - ssleay to libssl + if ( ($digit1 >= '3' && $digit2 >= '0' && $digit3 >= '0') + || ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0')) + { + my $dbgsuffix; + my $libsslpath; + my $libcryptopath; + + # The format name of the libraries is slightly + # different between the Win32 and Win64 platform, so + # adapt. + if (-e "$self->{options}->{openssl}/lib/VC/sslcrypto32MD.lib") + { + # Win32 here, with a debugging library set. + $dbgsuffix = 1; + $libsslpath = '\lib\VC\libssl32.lib'; + $libcryptopath = '\lib\VC\libcrypto32.lib'; + } + elsif (-e "$self->{options}->{openssl}/lib/VC/sslcrypto64MD.lib") + { + # Win64 here, with a debugging library set. + $dbgsuffix = 1; + $libsslpath = '\lib\VC\libssl64.lib'; + $libcryptopath = '\lib\VC\libcrypto64.lib'; + } + else + { + # On both Win32 and Win64 the same library + # names are used without a debugging context. + $dbgsuffix = 0; + $libsslpath = '\lib\libssl.lib'; + $libcryptopath = '\lib\libcrypto.lib'; + } + + $proj->AddLibrary($self->{options}->{openssl} . $libsslpath, + $dbgsuffix); + $proj->AddLibrary($self->{options}->{openssl} . $libcryptopath, + $dbgsuffix); + } + else + { + # Choose which set of libraries to use depending on if + # debugging libraries are in place in the installer. + if (-e "$self->{options}->{openssl}/lib/VC/ssleay32MD.lib") + { + $proj->AddLibrary( + $self->{options}->{openssl} . '\lib\VC\ssleay32.lib', 1); + $proj->AddLibrary( + $self->{options}->{openssl} . '\lib\VC\libeay32.lib', 1); + } + else + { + # We don't expect the config-specific library + # to be here, so don't ask for it in last + # parameter. + $proj->AddLibrary( + $self->{options}->{openssl} . '\lib\ssleay32.lib', 0); + $proj->AddLibrary( + $self->{options}->{openssl} . '\lib\libeay32.lib', 0); + } + } + } + if ($self->{options}->{nls}) + { + $proj->AddIncludeDir($self->{options}->{nls} . '\include'); + $proj->AddLibrary($self->{options}->{nls} . '\lib\libintl.lib'); + } + if ($self->{options}->{gss}) + { + $proj->AddIncludeDir($self->{options}->{gss} . '\include'); + $proj->AddIncludeDir($self->{options}->{gss} . '\include\krb5'); + if ($self->{platform} eq 'Win32') + { + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\i386\krb5_32.lib'); + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\i386\comerr32.lib'); + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\i386\gssapi32.lib'); + } + else + { + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\amd64\krb5_64.lib'); + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\amd64\comerr64.lib'); + $proj->AddLibrary( + $self->{options}->{gss} . '\lib\amd64\gssapi64.lib'); + } + } + if ($self->{options}->{iconv}) + { + $proj->AddIncludeDir($self->{options}->{iconv} . '\include'); + $proj->AddLibrary($self->{options}->{iconv} . '\lib\iconv.lib'); + } + if ($self->{options}->{icu}) + { + $proj->AddIncludeDir($self->{options}->{icu} . '\include'); + if ($self->{platform} eq 'Win32') + { + $proj->AddLibrary($self->{options}->{icu} . '\lib\icuin.lib'); + $proj->AddLibrary($self->{options}->{icu} . '\lib\icuuc.lib'); + $proj->AddLibrary($self->{options}->{icu} . '\lib\icudt.lib'); + } + else + { + $proj->AddLibrary($self->{options}->{icu} . '\lib64\icuin.lib'); + $proj->AddLibrary($self->{options}->{icu} . '\lib64\icuuc.lib'); + $proj->AddLibrary($self->{options}->{icu} . '\lib64\icudt.lib'); + } + } + if ($self->{options}->{xml}) + { + $proj->AddIncludeDir($self->{options}->{xml} . '\include'); + $proj->AddIncludeDir($self->{options}->{xml} . '\include\libxml2'); + $proj->AddLibrary($self->{options}->{xml} . '\lib\libxml2.lib'); + } + if ($self->{options}->{xslt}) + { + $proj->AddIncludeDir($self->{options}->{xslt} . '\include'); + $proj->AddLibrary($self->{options}->{xslt} . '\lib\libxslt.lib'); + } + if ($self->{options}->{lz4}) + { + $proj->AddIncludeDir($self->{options}->{lz4} . '\include'); + $proj->AddLibrary($self->{options}->{lz4} . '\lib\liblz4.lib'); + } + if ($self->{options}->{uuid}) + { + $proj->AddIncludeDir($self->{options}->{uuid} . '\include'); + $proj->AddLibrary($self->{options}->{uuid} . '\lib\uuid.lib'); + } + return $proj; +} + +sub Save +{ + my ($self) = @_; + my %flduid; + + $self->GenerateFiles(); + foreach my $fld (keys %{ $self->{projects} }) + { + foreach my $proj (@{ $self->{projects}->{$fld} }) + { + $proj->Save(); + } + } + + open(my $sln, '>', "pgsql.sln") || croak "Could not write to pgsql.sln\n"; + print $sln <<EOF; +Microsoft Visual Studio Solution File, Format Version $self->{solutionFileVersion} +# $self->{visualStudioName} +EOF + + print $sln $self->GetAdditionalHeaders(); + + foreach my $fld (keys %{ $self->{projects} }) + { + foreach my $proj (@{ $self->{projects}->{$fld} }) + { + print $sln <<EOF; +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "$proj->{name}", "$proj->{name}$proj->{filenameExtension}", "$proj->{guid}" +EndProject +EOF + } + if ($fld ne "") + { + $flduid{$fld} = $^O eq "MSWin32" ? Win32::GuidGen() : 'FAKE'; + print $sln <<EOF; +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "$fld", "$fld", "$flduid{$fld}" +EndProject +EOF + } + } + + print $sln <<EOF; +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|$self->{platform}= Debug|$self->{platform} + Release|$self->{platform} = Release|$self->{platform} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +EOF + + foreach my $fld (keys %{ $self->{projects} }) + { + foreach my $proj (@{ $self->{projects}->{$fld} }) + { + print $sln <<EOF; + $proj->{guid}.Debug|$self->{platform}.ActiveCfg = Debug|$self->{platform} + $proj->{guid}.Debug|$self->{platform}.Build.0 = Debug|$self->{platform} + $proj->{guid}.Release|$self->{platform}.ActiveCfg = Release|$self->{platform} + $proj->{guid}.Release|$self->{platform}.Build.0 = Release|$self->{platform} +EOF + } + } + + print $sln <<EOF; + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution +EOF + + foreach my $fld (keys %{ $self->{projects} }) + { + next if ($fld eq ""); + foreach my $proj (@{ $self->{projects}->{$fld} }) + { + print $sln "\t\t$proj->{guid} = $flduid{$fld}\n"; + } + } + + print $sln <<EOF; + EndGlobalSection +EndGlobal +EOF + close($sln); + return; +} + +sub GetFakeConfigure +{ + my $self = shift; + + my $cfg = '--enable-thread-safety'; + $cfg .= ' --enable-cassert' if ($self->{options}->{asserts}); + $cfg .= ' --enable-nls' if ($self->{options}->{nls}); + $cfg .= ' --enable-tap-tests' if ($self->{options}->{tap_tests}); + $cfg .= ' --with-ldap' if ($self->{options}->{ldap}); + $cfg .= ' --without-zlib' unless ($self->{options}->{zlib}); + $cfg .= ' --with-extra-version' if ($self->{options}->{extraver}); + $cfg .= ' --with-ssl=openssl' if ($self->{options}->{openssl}); + $cfg .= ' --with-uuid' if ($self->{options}->{uuid}); + $cfg .= ' --with-libxml' if ($self->{options}->{xml}); + $cfg .= ' --with-libxslt' if ($self->{options}->{xslt}); + $cfg .= ' --with-lz4' if ($self->{options}->{lz4}); + $cfg .= ' --with-gssapi' if ($self->{options}->{gss}); + $cfg .= ' --with-icu' if ($self->{options}->{icu}); + $cfg .= ' --with-tcl' if ($self->{options}->{tcl}); + $cfg .= ' --with-perl' if ($self->{options}->{perl}); + $cfg .= ' --with-python' if ($self->{options}->{python}); + my $port = $self->{options}->{'--with-pgport'}; + $cfg .= " --with-pgport=$port" if defined($port); + + return $cfg; +} + +package VS2013Solution; + +# +# Package that encapsulates a Visual Studio 2013 solution file +# + +use Carp; +use strict; +use warnings; +use base qw(Solution); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{solutionFileVersion} = '12.00'; + $self->{vcver} = '12.00'; + $self->{visualStudioName} = 'Visual Studio 2013'; + $self->{VisualStudioVersion} = '12.0.21005.1'; + $self->{MinimumVisualStudioVersion} = '10.0.40219.1'; + + return $self; +} + +package VS2015Solution; + +# +# Package that encapsulates a Visual Studio 2015 solution file +# + +use Carp; +use strict; +use warnings; +use base qw(Solution); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{solutionFileVersion} = '12.00'; + $self->{vcver} = '14.00'; + $self->{visualStudioName} = 'Visual Studio 2015'; + $self->{VisualStudioVersion} = '14.0.24730.2'; + $self->{MinimumVisualStudioVersion} = '10.0.40219.1'; + + return $self; +} + +package VS2017Solution; + +# +# Package that encapsulates a Visual Studio 2017 solution file +# + +use Carp; +use strict; +use warnings; +use base qw(Solution); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{solutionFileVersion} = '12.00'; + $self->{vcver} = '15.00'; + $self->{visualStudioName} = 'Visual Studio 2017'; + $self->{VisualStudioVersion} = '15.0.26730.3'; + $self->{MinimumVisualStudioVersion} = '10.0.40219.1'; + + return $self; +} + +package VS2019Solution; + +# +# Package that encapsulates a Visual Studio 2019 solution file +# + +use Carp; +use strict; +use warnings; +use base qw(Solution); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{solutionFileVersion} = '12.00'; + $self->{vcver} = '16.00'; + $self->{visualStudioName} = 'Visual Studio 2019'; + $self->{VisualStudioVersion} = '16.0.28729.10'; + $self->{MinimumVisualStudioVersion} = '10.0.40219.1'; + + return $self; +} + +package VS2022Solution; + +# +# Package that encapsulates a Visual Studio 2022 solution file +# + +use Carp; +use strict; +use warnings; +use base qw(Solution); + +no warnings qw(redefine); ## no critic + +sub new +{ + my $classname = shift; + my $self = $classname->SUPER::_new(@_); + bless($self, $classname); + + $self->{solutionFileVersion} = '12.00'; + $self->{vcver} = '17.00'; + $self->{visualStudioName} = 'Visual Studio 2022'; + $self->{VisualStudioVersion} = '17.0.31903.59'; + $self->{MinimumVisualStudioVersion} = '10.0.40219.1'; + + return $self; +} + +sub GetAdditionalHeaders +{ + my ($self, $f) = @_; + + return qq|VisualStudioVersion = $self->{VisualStudioVersion} +MinimumVisualStudioVersion = $self->{MinimumVisualStudioVersion} +|; +} + +1; diff --git a/src/tools/msvc/VSObjectFactory.pm b/src/tools/msvc/VSObjectFactory.pm new file mode 100644 index 0000000..a865604 --- /dev/null +++ b/src/tools/msvc/VSObjectFactory.pm @@ -0,0 +1,182 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package VSObjectFactory; + +# +# Package that creates Visual Studio wrapper objects for msvc build +# +# src/tools/msvc/VSObjectFactory.pm +# + +use Carp; +use strict; +use warnings; + +use Exporter; +use Project; +use Solution; +use MSBuildProject; + +our (@ISA, @EXPORT); +@ISA = qw(Exporter); +@EXPORT = qw(CreateSolution CreateProject DetermineVisualStudioVersion); + +no warnings qw(redefine); ## no critic + +sub CreateSolution +{ + my $visualStudioVersion = shift; + + if (!defined($visualStudioVersion)) + { + $visualStudioVersion = DetermineVisualStudioVersion(); + } + + if ($visualStudioVersion eq '12.00') + { + return new VS2013Solution(@_); + } + elsif ($visualStudioVersion eq '14.00') + { + return new VS2015Solution(@_); + } + + # The version of nmake bundled in Visual Studio 2017 is greater + # than 14.10 and less than 14.20. And the version number is + # actually 15.00. + elsif ( + ($visualStudioVersion ge '14.10' && $visualStudioVersion lt '14.20') + || $visualStudioVersion eq '15.00') + { + return new VS2017Solution(@_); + } + + # The version of nmake bundled in Visual Studio 2019 is greater + # than 14.20 and less than 14.30. And the version number is + # actually 16.00. + elsif ( + ($visualStudioVersion ge '14.20' && $visualStudioVersion lt '14.30') + || $visualStudioVersion eq '16.00') + { + return new VS2019Solution(@_); + } + + # The version of nmake bundled in Visual Studio 2022 is greater + # than 14.30 and less than 14.40. And the version number is + # actually 17.00. + elsif ( + ($visualStudioVersion ge '14.30' && $visualStudioVersion lt '14.40') + || $visualStudioVersion eq '17.00') + { + return new VS2022Solution(@_); + } + else + { + croak + "The requested Visual Studio version $visualStudioVersion is not supported."; + } +} + +sub CreateProject +{ + my $visualStudioVersion = shift; + + if (!defined($visualStudioVersion)) + { + $visualStudioVersion = DetermineVisualStudioVersion(); + } + + if ($visualStudioVersion eq '12.00') + { + return new VC2013Project(@_); + } + elsif ($visualStudioVersion eq '14.00') + { + return new VC2015Project(@_); + } + + # The version of nmake bundled in Visual Studio 2017 is greater + # than 14.10 and less than 14.20. And the version number is + # actually 15.00. + elsif ( + ($visualStudioVersion ge '14.10' && $visualStudioVersion lt '14.20') + || $visualStudioVersion eq '15.00') + { + return new VC2017Project(@_); + } + + # The version of nmake bundled in Visual Studio 2019 is greater + # than 14.20 and less than 14.30. And the version number is + # actually 16.00. + elsif ( + ($visualStudioVersion ge '14.20' && $visualStudioVersion lt '14.30') + || $visualStudioVersion eq '16.00') + { + return new VC2019Project(@_); + } + + # The version of nmake bundled in Visual Studio 2022 is greater + # than 14.30 and less than 14.40. And the version number is + # actually 17.00. + elsif ( + ($visualStudioVersion ge '14.30' && $visualStudioVersion lt '14.40') + || $visualStudioVersion eq '17.00') + { + return new VC2022Project(@_); + } + else + { + croak + "The requested Visual Studio version $visualStudioVersion is not supported."; + } +} + +sub DetermineVisualStudioVersion +{ + if ($^O eq "MSWin32") + { + # To determine version of Visual Studio we use nmake as it has + # existed for a long time and still exists in current Visual + # Studio versions. + my $output = `nmake /? 2>&1`; + $? >> 8 == 0 + or croak + "Unable to determine Visual Studio version: The nmake command wasn't found."; + if ($output =~ /(\d+)\.(\d+)\.\d+(\.\d+)?/) + { + return _GetVisualStudioVersion($1, $2); + } + + croak + "Unable to determine Visual Studio version: The nmake version could not be determined."; + } + else + { + # fake version + return '17.00'; + } +} + +sub _GetVisualStudioVersion +{ + my ($major, $minor) = @_; + + # The major visual studio that is supported has nmake + # version <= 14.40, so stick with it as the latest version + # if bumping on something even newer. + if ($major >= 14 && $minor >= 40) + { + carp + "The determined version of Visual Studio is newer than the latest supported version. Returning the latest supported version instead."; + return '14.30'; + } + elsif ($major < 12) + { + croak + "Unable to determine Visual Studio version: Visual Studio versions before 12.0 aren't supported."; + } + return "$major.$minor"; +} + +1; diff --git a/src/tools/msvc/build.bat b/src/tools/msvc/build.bat new file mode 100755 index 0000000..171f749 --- /dev/null +++ b/src/tools/msvc/build.bat @@ -0,0 +1,6 @@ +@echo off +REM src/tools/msvc/build.bat +REM all the logic for this now belongs in build.pl. This file really +REM only exists so you don't have to type "perl build.pl" +REM Resist any temptation to add any logic here. +@perl %~dp0/build.pl %* diff --git a/src/tools/msvc/build.pl b/src/tools/msvc/build.pl new file mode 100644 index 0000000..4c51d8b --- /dev/null +++ b/src/tools/msvc/build.pl @@ -0,0 +1,92 @@ +# -*-perl-*- hey - emacs - this is a perl file + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# +# Script that provides 'make' functionality for msvc builds. +# +# src/tools/msvc/build.pl +# +use strict; +use warnings; + +use FindBin; +use lib $FindBin::RealBin; + +use Cwd; + +use Mkvcbuild; + +sub usage +{ + die( "Usage: build.pl [ [ <configuration> ] <component> ]\n" + . "Options are case-insensitive.\n" + . " configuration: Release | Debug. This sets the configuration\n" + . " to build. Default is Release.\n" + . " component: name of component to build. An empty value means\n" + . " to build all components.\n"); +} + +chdir('../../..') if (-d '../msvc' && -d '../../../src'); +die 'Must run from root or msvc directory' + unless (-d 'src/tools/msvc' && -d 'src'); + +usage() unless scalar(@ARGV) <= 2; + +# buildenv.pl is for specifying the build environment settings +# it should contain lines like: +# $ENV{PATH} = "c:/path/to/bison/bin;$ENV{PATH}"; + +if (-e "src/tools/msvc/buildenv.pl") +{ + do "./src/tools/msvc/buildenv.pl"; +} +elsif (-e "./buildenv.pl") +{ + do "./buildenv.pl"; +} + +# set up the project +our $config; +do "./src/tools/msvc/config_default.pl"; +do "./src/tools/msvc/config.pl" if (-f "src/tools/msvc/config.pl"); + +my $vcver = Mkvcbuild::mkvcbuild($config); + +# check what sort of build we are doing +my $bconf = $ENV{CONFIG} || "Release"; +my $msbflags = $ENV{MSBFLAGS} || ""; +my $buildwhat = $ARGV[1] || ""; + +if (defined($ARGV[0])) +{ + if (uc($ARGV[0]) eq 'DEBUG') + { + $bconf = "Debug"; + } + elsif (uc($ARGV[0]) ne "RELEASE") + { + $buildwhat = $ARGV[0] || ""; + } +} + +# ... and do it + +if ($buildwhat) +{ + system( + "msbuild $buildwhat.vcxproj /verbosity:normal $msbflags /p:Configuration=$bconf" + ); +} +else +{ + system( + "msbuild pgsql.sln /verbosity:normal $msbflags /p:Configuration=$bconf" + ); +} + +# report status + +my $status = $? >> 8; + +exit $status; diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat new file mode 100755 index 0000000..42b3d1b --- /dev/null +++ b/src/tools/msvc/clean.bat @@ -0,0 +1,142 @@ +@echo off +REM src/tools/msvc/clean.bat + +set DIST=0 +if "%1"=="dist" set DIST=1 + +setlocal + +cd "%~dp0\..\..\.." + +if exist debug rd /s /q debug +if exist release rd /s /q release +for %%f in (*.vcproj) do del %%f +for %%f in (*.vcxproj) do del %%f +for %%f in (*.vcxproj.user) do del %%f +if exist pgsql.sln del /q pgsql.sln +if exist pgsql.sln.cache del /q pgsql.sln.cache +if exist pgsql.sdf del /q pgsql.sdf +if exist pgsql.suo del /q /a:H pgsql.suo +del /s /q src\bin\win32ver.rc 2> NUL +del /s /q src\interfaces\win32ver.rc 2> NUL +if exist src\backend\win32ver.rc del /q src\backend\win32ver.rc +if exist src\backend\replication\libpqwalreceiver\win32ver.rc del /q src\backend\replication\libpqwalreceiver\win32ver.rc +if exist src\backend\replication\pgoutput\win32ver.rc del /q src\backend\replication\pgoutput\win32ver.rc +if exist src\backend\snowball\win32ver.rc del /q src\backend\snowball\win32ver.rc +if exist src\interfaces\ecpg\test\win32ver.rc del /q src\interfaces\ecpg\test\win32ver.rc +if exist src\pl\plperl\win32ver.rc del /q src\pl\plperl\win32ver.rc +if exist src\pl\plpgsql\src\win32ver.rc del /q src\pl\plpgsql\src\win32ver.rc +if exist src\pl\plpython\win32ver.rc del /q src\pl\plpython\win32ver.rc +if exist src\pl\tcl\win32ver.rc del /q src\pl\tcl\win32ver.rc +if exist src\test\isolation\win32ver.rc del /q src\test\isolation\win32ver.rc +if exist src\test\regress\win32ver.rc del /q src\test\regress\win32ver.rc +if exist src\timezone\win32ver.rc del /q src\timezone\win32ver.rc + +for /d %%f in (src\interfaces\ecpg\*) do if exist %%f\win32ver.rc del /q %%f\win32ver.rc +for /d %%f in (contrib\*) do if exist %%f\win32ver.rc del /q %%f\win32ver.rc +for /d %%f in (src\backend\utils\mb\conversion_procs\*) do if exist %%f\win32ver.rc del /q %%f\win32ver.rc +for /d %%f in (src\test\modules\*) do if exist %%f\win32ver.rc del /q %%f\win32ver.rc + +REM Delete files created with GenerateFiles() in Solution.pm +if exist src\include\pg_config.h del /q src\include\pg_config.h +if exist src\include\pg_config_ext.h del /q src\include\pg_config_ext.h +if exist src\include\pg_config_os.h del /q src\include\pg_config_os.h +if %DIST%==1 if exist src\backend\parser\gram.h del /q src\backend\parser\gram.h +if exist src\include\utils\errcodes.h del /q src\include\utils\errcodes.h +if exist src\include\utils\fmgroids.h del /q src\include\utils\fmgroids.h +if exist src\include\utils\fmgrprotos.h del /q src\include\utils\fmgrprotos.h +if exist src\include\storage\lwlocknames.h del /q src\include\storage\lwlocknames.h +if exist src\include\utils\probes.h del /q src\include\utils\probes.h +if exist src\include\catalog\schemapg.h del /q src\include\catalog\schemapg.h +if exist src\include\catalog\system_fk_info.h del /q src\include\catalog\system_fk_info.h +if exist src\include\catalog\pg_*_d.h del /q src\include\catalog\pg_*_d.h +if exist src\include\catalog\header-stamp del /q src\include\catalog\header-stamp +if exist doc\src\sgml\version.sgml del /q doc\src\sgml\version.sgml + +if %DIST%==1 if exist src\backend\utils\fmgroids.h del /q src\backend\utils\fmgroids.h +if %DIST%==1 if exist src\backend\utils\fmgrprotos.h del /q src\backend\utils\fmgrprotos.h +if %DIST%==1 if exist src\backend\utils\fmgrtab.c del /q src\backend\utils\fmgrtab.c +if %DIST%==1 if exist src\backend\utils\fmgr-stamp del /q src\backend\utils\fmgr-stamp +if %DIST%==1 if exist src\backend\utils\errcodes.h del /q src\backend\utils\errcodes.h +if %DIST%==1 if exist src\backend\storage\lmgr\lwlocknames.c del /q src\backend\storage\lmgr\lwlocknames.c +if %DIST%==1 if exist src\backend\storage\lmgr\lwlocknames.h del /q src\backend\storage\lmgr\lwlocknames.h +if %DIST%==1 if exist src\pl\plpython\spiexceptions.h del /q src\pl\plpython\spiexceptions.h +if %DIST%==1 if exist src\pl\plpgsql\src\plerrcodes.h del /q src\pl\plpgsql\src\plerrcodes.h +if %DIST%==1 if exist src\pl\tcl\pltclerrcodes.h del /q src\pl\tcl\pltclerrcodes.h +if %DIST%==1 if exist src\bin\psql\sql_help.c del /q src\bin\psql\sql_help.c +if %DIST%==1 if exist src\bin\psql\sql_help.h del /q src\bin\psql\sql_help.h +if %DIST%==1 if exist src\common\kwlist_d.h del /q src\common\kwlist_d.h +if %DIST%==1 if exist src\pl\plpgsql\src\pl_reserved_kwlist_d.h del /q src\pl\plpgsql\src\pl_reserved_kwlist_d.h +if %DIST%==1 if exist src\pl\plpgsql\src\pl_unreserved_kwlist_d.h del /q src\pl\plpgsql\src\pl_unreserved_kwlist_d.h +if %DIST%==1 if exist src\interfaces\ecpg\preproc\c_kwlist_d.h del /q src\interfaces\ecpg\preproc\c_kwlist_d.h +if %DIST%==1 if exist src\interfaces\ecpg\preproc\ecpg_kwlist_d.h del /q src\interfaces\ecpg\preproc\ecpg_kwlist_d.h +if %DIST%==1 if exist src\interfaces\ecpg\preproc\preproc.y del /q src\interfaces\ecpg\preproc\preproc.y +if %DIST%==1 if exist src\backend\catalog\postgres.bki del /q src\backend\catalog\postgres.bki +if %DIST%==1 if exist src\backend\catalog\system_constraints.sql del /q src\backend\catalog\system_constraints.sql +if %DIST%==1 if exist src\backend\catalog\schemapg.h del /q src\backend\catalog\schemapg.h +if %DIST%==1 if exist src\backend\catalog\system_fk_info.h del /q src\backend\catalog\system_fk_info.h +if %DIST%==1 if exist src\backend\catalog\pg_*_d.h del /q src\backend\catalog\pg_*_d.h +if %DIST%==1 if exist src\backend\catalog\bki-stamp del /q src\backend\catalog\bki-stamp +if %DIST%==1 if exist src\backend\parser\scan.c del /q src\backend\parser\scan.c +if %DIST%==1 if exist src\backend\parser\gram.c del /q src\backend\parser\gram.c +if %DIST%==1 if exist src\backend\bootstrap\bootscanner.c del /q src\backend\bootstrap\bootscanner.c +if %DIST%==1 if exist src\backend\bootstrap\bootparse.c del /q src\backend\bootstrap\bootparse.c +if %DIST%==1 if exist src\backend\utils\adt\jsonpath_gram.c del /q src\backend\utils\adt\jsonpath_gram.c +if %DIST%==1 if exist src\backend\utils\adt\jsonpath_scan.c del /q src\backend\utils\adt\jsonpath_scan.c +if %DIST%==1 if exist src\backend\utils\misc\guc-file.c del /q src\backend\utils\misc\guc-file.c +if %DIST%==1 if exist src\backend\replication\repl_scanner.c del /q src\backend\replication\repl_scanner.c +if %DIST%==1 if exist src\backend\replication\repl_gram.c del /q src\backend\replication\repl_gram.c +if %DIST%==1 if exist src\backend\replication\syncrep_scanner.c del /q src\backend\replication\syncrep_scanner.c +if %DIST%==1 if exist src\backend\replication\syncrep_gram.c del /q src\backend\replication\syncrep_gram.c + + +if exist src\interfaces\libpq\libpqdll.def del /q src\interfaces\libpq\libpqdll.def +if exist src\interfaces\ecpg\compatlib\compatlib.def del /q src\interfaces\ecpg\compatlib\compatlib.def +if exist src\interfaces\ecpg\ecpglib\ecpglib.def del /q src\interfaces\ecpg\ecpglib\ecpglib.def +if exist src\interfaces\ecpg\include\ecpg_config.h del /q src\interfaces\ecpg\include\ecpg_config.h +if exist src\interfaces\ecpg\pgtypeslib\pgtypeslib.def del /q src\interfaces\ecpg\pgtypeslib\pgtypeslib.def +if %DIST%==1 if exist src\interfaces\ecpg\preproc\pgc.c del /q src\interfaces\ecpg\preproc\pgc.c +if %DIST%==1 if exist src\interfaces\ecpg\preproc\preproc.c del /q src\interfaces\ecpg\preproc\preproc.c +if %DIST%==1 if exist src\interfaces\ecpg\preproc\preproc.h del /q src\interfaces\ecpg\preproc\preproc.h + +if exist src\port\pg_config_paths.h del /q src\port\pg_config_paths.h + +if exist src\pl\plperl\SPI.c del /q src\pl\plperl\SPI.c +if exist src\pl\plperl\Util.c del /q src\pl\plperl\Util.c +if exist src\pl\plperl\perlchunks.h del /q src\pl\plperl\perlchunks.h +if exist src\pl\plperl\plperl_opmask.h del /q src\pl\plperl\plperl_opmask.h +if %DIST%==1 if exist src\pl\plpgsql\src\pl_gram.c del /q src\pl\plpgsql\src\pl_gram.c +if %DIST%==1 if exist src\pl\plpgsql\src\pl_gram.h del /q src\pl\plpgsql\src\pl_gram.h + +if %DIST%==1 if exist src\fe_utils\psqlscan.c del /q src\fe_utils\psqlscan.c +if %DIST%==1 if exist src\bin\psql\psqlscanslash.c del /q src\bin\psql\psqlscanslash.c +if %DIST%==1 if exist src\bin\pgbench\exprscan.c del /q src\bin\pgbench\exprscan.c +if %DIST%==1 if exist src\bin\pgbench\exprparse.c del /q src\bin\pgbench\exprparse.c + +if %DIST%==1 if exist contrib\cube\cubescan.c del /q contrib\cube\cubescan.c +if %DIST%==1 if exist contrib\cube\cubeparse.c del /q contrib\cube\cubeparse.c +if %DIST%==1 if exist contrib\seg\segscan.c del /q contrib\seg\segscan.c +if %DIST%==1 if exist contrib\seg\segparse.c del /q contrib\seg\segparse.c + +if exist src\test\regress\tmp_check rd /s /q src\test\regress\tmp_check +if exist contrib\spi\refint.dll del /q contrib\spi\refint.dll +if exist contrib\spi\autoinc.dll del /q contrib\spi\autoinc.dll +if exist src\test\regress\regress.dll del /q src\test\regress\regress.dll +if exist src\test\regress\refint.dll del /q src\test\regress\refint.dll +if exist src\test\regress\autoinc.dll del /q src\test\regress\autoinc.dll +if %DIST%==1 if exist src\test\isolation\specscanner.c del /q src\test\isolation\specscanner.c +if %DIST%==1 if exist src\test\isolation\specparse.c del /q src\test\isolation\specparse.c + +for /d %%f in (contrib\* src\bin\* src\test\* src\test\modules\* + ) do if exist %%f\tmp_check rd /s /q %%f\tmp_check + +REM Clean up datafiles built with contrib +REM cd contrib +REM for /r %%f in (*.sql) do if exist %%f.in del %%f + +cd "%~dp0" + +REM Clean up ecpg regression test files +msbuild ecpg_regression.proj /NoLogo /v:q %MSBFLAGS% /t:clean + +goto :eof diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl new file mode 100644 index 0000000..460c037 --- /dev/null +++ b/src/tools/msvc/config_default.pl @@ -0,0 +1,31 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# Configuration arguments for vcbuild. +use strict; +use warnings; + +our $config = { + asserts => 0, # --enable-cassert + + # blocksize => 8, # --with-blocksize, 8kB by default + # wal_blocksize => 8, # --with-wal-blocksize, 8kB by default + ldap => 1, # --with-ldap + extraver => undef, # --with-extra-version=<string> + gss => undef, # --with-gssapi=<path> + icu => undef, # --with-icu=<path> + lz4 => undef, # --with-lz4=<path> + nls => undef, # --enable-nls=<path> + tap_tests => undef, # --enable-tap-tests + tcl => undef, # --with-tcl=<path> + perl => undef, # --with-perl=<path> + python => undef, # --with-python=<path> + openssl => undef, # --with-ssl=openssl with <path> + uuid => undef, # --with-uuid=<path> + xml => undef, # --with-libxml=<path> + xslt => undef, # --with-libxslt=<path> + iconv => undef, # (not in configure, path to iconv) + zlib => undef # --with-zlib=<path> +}; + +1; diff --git a/src/tools/msvc/dummylib/README b/src/tools/msvc/dummylib/README new file mode 100644 index 0000000..7b63d0e --- /dev/null +++ b/src/tools/msvc/dummylib/README @@ -0,0 +1,13 @@ + +src/tools/msvc/dummylib + +This directory contains just enough of a dummy library to allow checking of +the programs in src/tools/msvc and src/tools/win32tzlist.pl with +perl -cw, even on machines that lack the Win32 perl infrastructure. + +invoke via: + +PERL5LIB=src/tools/msvc/dummylib perl -cw $file + +This is the only use that should be made of this directory. Attempting actually +running of any programs using this library will result in a lot of grief. diff --git a/src/tools/msvc/dummylib/Win32.pm b/src/tools/msvc/dummylib/Win32.pm new file mode 100644 index 0000000..d47e59a --- /dev/null +++ b/src/tools/msvc/dummylib/Win32.pm @@ -0,0 +1,7 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Win32; +use strict; +use warnings; +1; diff --git a/src/tools/msvc/dummylib/Win32/Registry.pm b/src/tools/msvc/dummylib/Win32/Registry.pm new file mode 100644 index 0000000..73bd898 --- /dev/null +++ b/src/tools/msvc/dummylib/Win32/Registry.pm @@ -0,0 +1,16 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Win32::Registry; + +use strict; +use warnings; + +use vars qw($HKEY_LOCAL_MACHINE); + +use Exporter (); +our (@EXPORT, @ISA); +@ISA = qw(Exporter); +@EXPORT = qw($HKEY_LOCAL_MACHINE); + +1; diff --git a/src/tools/msvc/dummylib/Win32API/File.pm b/src/tools/msvc/dummylib/Win32API/File.pm new file mode 100644 index 0000000..b74d28f --- /dev/null +++ b/src/tools/msvc/dummylib/Win32API/File.pm @@ -0,0 +1,17 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +package Win32API::File; + +use strict; +use warnings; + +use constant { SEM_FAILCRITICALERRORS => 1, SEM_NOGPFAULTERRORBOX => 2 }; +sub SetErrormode { } +use Exporter; +our (@ISA, @EXPORT_OK, %EXPORT_TAGS); +@ISA = qw(Exporter); +@EXPORT_OK = qw(SetErrorMode SEM_FAILCRITICALERRORS SEM_NOGPFAULTERRORBOX); +%EXPORT_TAGS = (SEM_ => [qw(SEM_FAILCRITICALERRORS SEM_NOGPFAULTERRORBOX)]); + +1; diff --git a/src/tools/msvc/ecpg_regression.proj b/src/tools/msvc/ecpg_regression.proj new file mode 100644 index 0000000..ec2760b --- /dev/null +++ b/src/tools/msvc/ecpg_regression.proj @@ -0,0 +1,64 @@ +<?xml version="1.0"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="all"> +<!-- + MSBuild project file to build ecpg regression tests +--> + + <PropertyGroup> + <TESTDIR>..\..\interfaces\ecpg\test</TESTDIR> + <CONFIG>Debug</CONFIG> + <OUTDIR>..\..\..\..\..\$(CONFIG)\</OUTDIR> + </PropertyGroup> + <PropertyGroup Condition="'$(CONFIG)'=='DEBUG'"> + <!-- set debug runtime library if necessary to be compatible with the LIB files generated --> + <DEBUGLIB>d</DEBUGLIB> + </PropertyGroup> + + <ItemGroup> + <Pgc Include="$(TESTDIR)\**\*.pgc" Exclude="$(TESTDIR)\performance\perftest.pgc"/> + </ItemGroup> + + <ItemGroup> + <OutputToDelete Include="$(TESTDIR)\**\*.exe" /> + <OutputToDelete Include="$(TESTDIR)\**\*.exe.manifest" /> + <OutputToDelete Include="$(TESTDIR)\**\*.obj" /> + <OutputToDelete Include="$(TESTDIR)\**\*.c" Exclude="$(TESTDIR)\pg_regress_ecpg.c;$(TESTDIR)\expected\*.c" /> + </ItemGroup> + + <Target Name="all" Inputs="@(Pgc)" Outputs="%(RelativeDir)%(Filename).exe"> + <!-- Set special parameters for some tests --> + <CreateProperty Value="-C INFORMIX" Condition="'%(Pgc.RelativeDir)'=='$(TESTDIR)\compat_informix\'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-C INFORMIX -r no_indicator" Condition="'%(Pgc.FileName)'=='rnull'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-C ORACLE" Condition="'%(Pgc.RelativeDir)'=='$(TESTDIR)\compat_oracle\'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-c" Condition="'%(Pgc.FileName)'=='array_of_struct'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-c" Condition="'%(Pgc.FileName)'=='pointer_to_struct'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-r questionmarks" Condition="'%(Pgc.FileName)'=='oldexec'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-r prepare" Condition="'%(Pgc.FileName)'=='autoprep'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + <CreateProperty Value="-i" Condition="'%(Pgc.FileName)'=='strings'"> + <Output TaskParameter="Value" PropertyName="ECPGPARAM" /> + </CreateProperty> + + <!-- Run ECPG and the Visual C++ compiler on the files. Don't bother with dependency check between the steps --> + <Exec WorkingDirectory="%(Pgc.RelativeDir)" Command="$(OUTDIR)ecpg\ecpg -I ../../include --regression $(ECPGPARAM) -o %(Pgc.Filename).c %(Pgc.Filename).pgc" /> + <Exec WorkingDirectory="%(Pgc.RelativeDir)" Command="cl /nologo %(Pgc.FileName).c /TC /MD$(DEBUGLIB) /DENABLE_THREAD_SAFETY /DWIN32 /I. /I..\..\include /I..\..\..\libpq /I..\..\..\..\include /link /defaultlib:$(OUTDIR)libecpg\libecpg.lib /defaultlib:$(OUTDIR)libecpg_compat\libecpg_compat.lib /defaultlib:$(OUTDIR)libpgtypes\libpgtypes.lib" /> + </Target> + + <!-- Clean up all output files --> + <Target Name="clean"> + <Delete Files="@(OutputToDelete)" /> + </Target> +</Project> diff --git a/src/tools/msvc/gendef.pl b/src/tools/msvc/gendef.pl new file mode 100644 index 0000000..71c1858 --- /dev/null +++ b/src/tools/msvc/gendef.pl @@ -0,0 +1,181 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +use strict; +use warnings; +use 5.8.0; +use List::Util qw(max); + +my @def; + +# +# Script that generates a .DEF file for all objects in a directory +# +# src/tools/msvc/gendef.pl +# + +# Given a symbol file path, loops over its contents +# and returns a list of symbols of interest as a dictionary +# of 'symbolname' -> symtype, where symtype is: +# +# 0 a CODE symbol, left undecorated in the .DEF +# 1 A DATA symbol, i.e. global var export +# +sub extract_syms +{ + my ($symfile, $def) = @_; + open(my $f, '<', $symfile) || die "Could not open $symfile for $_: $!\n"; + while (<$f>) + { + + # Expected symbol lines look like: + # + # 0 1 2 3 4 5 6 + # IDX SYMBOL SECT SYMTYPE SYMSTATIC SYMNAME + # ------------------------------------------------------------------------ + # 02E 00000130 SECTA notype External | _standbyState + # 02F 00000009 SECT9 notype Static | _LocalRecoveryInProgress + # 064 00000020 SECTC notype () Static | _XLogCheckBuffer + # 065 00000000 UNDEF notype () External | _BufferGetTag + # + # See http://msdn.microsoft.com/en-us/library/b842y285.aspx + # + # We're not interested in the symbol index or offset. + # + # SECT[ION] is only examined to see whether the symbol is defined in a + # COFF section of the local object file; if UNDEF, it's a symbol to be + # resolved at link time from another object so we can't export it. + # + # SYMTYPE is always notype for C symbols as there's no typeinfo and no + # way to get the symbol type from name (de)mangling. However, we care + # if "notype" is suffixed by "()" or not. The presence of () means the + # symbol is a function, the absence means it isn't. + # + # SYMSTATIC indicates whether it's a compilation-unit local "static" + # symbol ("Static"), or whether it's available for use from other + # compilation units ("External"). We export all symbols that aren't + # static as part of the whole program DLL interface to produce UNIX-like + # default linkage. + # + # SYMNAME is, obviously, the symbol name. The leading underscore + # indicates that the _cdecl calling convention is used. See + # http://www.unixwiz.net/techtips/win32-callconv.html + # http://www.codeproject.com/Articles/1388/Calling-Conventions-Demystified + # + s/notype \(\)/func/g; + s/notype/data/g; + + my @pieces = split; + + # Skip file and section headers and other non-symbol entries + next unless defined($pieces[0]) and $pieces[0] =~ /^[A-F0-9]{3,}$/; + + # Skip blank symbol names + next unless $pieces[6]; + + # Skip externs used from another compilation unit + next if ($pieces[2] eq "UNDEF"); + + # Skip static symbols + next unless ($pieces[4] eq "External"); + + # Skip some more MSVC-generated crud + next if $pieces[6] =~ /^@/; + next if $pieces[6] =~ /^\(/; + + # __real and __xmm are out-of-line floating point literals and + # (for __xmm) their SIMD equivalents. They shouldn't be part + # of the DLL interface. + next if $pieces[6] =~ /^__real/; + next if $pieces[6] =~ /^__xmm/; + + # __imp entries are imports from other DLLs, eg __imp__malloc . + # (We should never have one of these that hasn't already been skipped + # by the UNDEF test above, though). + next if $pieces[6] =~ /^__imp/; + + # More under-documented internal crud + next if $pieces[6] =~ /NULL_THUNK_DATA$/; + next if $pieces[6] =~ /^__IMPORT_DESCRIPTOR/; + next if $pieces[6] =~ /^__NULL_IMPORT/; + + # Skip string literals + next if $pieces[6] =~ /^\?\?_C/; + + # We assume that if a symbol is defined as data, then as a function, + # the linker will reject the binary anyway. So it's OK to just pick + # whatever came last. + $def->{ $pieces[6] } = $pieces[3]; + } + close($f); + return; +} + +sub writedef +{ + my ($deffile, $platform, $def) = @_; + open(my $fh, '>', $deffile) || die "Could not write to $deffile\n"; + print $fh "EXPORTS\n"; + foreach my $f (sort keys %{$def}) + { + my $isdata = $def->{$f} eq 'data'; + + # Strip the leading underscore for win32, but not x64 + $f =~ s/^_// + unless ($platform eq "x64"); + + # Emit just the name if it's a function symbol, or emit the name + # decorated with the DATA option for variables. + if ($isdata) + { + print $fh " $f DATA\n"; + } + else + { + print $fh " $f\n"; + } + } + close($fh); + return; +} + + +sub usage +{ + die( "Usage: gendef.pl <modulepath> <platform>\n" + . " modulepath: path to dir with obj files, no trailing slash" + . " platform: Win32 | x64"); +} + +usage() + unless scalar(@ARGV) == 2 + && ( ($ARGV[0] =~ /\\([^\\]+$)/) + && ($ARGV[1] eq 'Win32' || $ARGV[1] eq 'x64')); +my $defname = uc $1; +my $deffile = "$ARGV[0]/$defname.def"; +my $platform = $ARGV[1]; + +# if the def file exists and is newer than all input object files, skip +# its creation +if (-f $deffile + && (-M $deffile > max(map { -M } <$ARGV[0]/*.obj>))) +{ + print "Not re-generating $defname.DEF, file already exists.\n"; + exit(0); +} + +print "Generating $defname.DEF from directory $ARGV[0], platform $platform\n"; + +my %def = (); + +my $symfile = "$ARGV[0]/all.sym"; +my $tmpfile = "$ARGV[0]/tmp.sym"; +system("dumpbin /symbols /out:$tmpfile $ARGV[0]/*.obj >NUL") + && die "Could not call dumpbin"; +rename($tmpfile, $symfile); +extract_syms($symfile, \%def); +print "\n"; + +writedef($deffile, $platform, \%def); + +print "Generated " . scalar(keys(%def)) . " symbols\n"; diff --git a/src/tools/msvc/install.bat b/src/tools/msvc/install.bat new file mode 100644 index 0000000..d02b808 --- /dev/null +++ b/src/tools/msvc/install.bat @@ -0,0 +1,6 @@ +@echo off +REM src/tools/msvc/install.bat +REM all the logic for this now belongs in install.pl. This file really +REM only exists so you don't have to type "perl install.pl" +REM Resist any temptation to add any logic here. +@perl %~dp0/install.pl %* diff --git a/src/tools/msvc/install.pl b/src/tools/msvc/install.pl new file mode 100755 index 0000000..45276d0 --- /dev/null +++ b/src/tools/msvc/install.pl @@ -0,0 +1,39 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# +# Script that provides 'make install' functionality for msvc builds +# +# src/tools/msvc/install.pl +# +use strict; +use warnings; + +use FindBin; +use lib $FindBin::RealBin; + +use Install qw(Install); + +# buildenv.pl is for specifying the build environment settings +# it should contain lines like: +# $ENV{PATH} = "c:/path/to/bison/bin;$ENV{PATH}"; + +if (-e "src/tools/msvc/buildenv.pl") +{ + do "./src/tools/msvc/buildenv.pl"; +} +elsif (-e "./buildenv.pl") +{ + do "./buildenv.pl"; +} + +my $target = shift || Usage(); +my $insttype = shift; +Install($target, $insttype); + +sub Usage +{ + print "Usage: install.pl <targetdir> [installtype]\n"; + print "installtype: client\n"; + exit(1); +} diff --git a/src/tools/msvc/mkvcbuild.pl b/src/tools/msvc/mkvcbuild.pl new file mode 100644 index 0000000..3749e7f --- /dev/null +++ b/src/tools/msvc/mkvcbuild.pl @@ -0,0 +1,31 @@ + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# +# Script that parses Unix style build environment and generates build files +# for building with Visual Studio. +# +# src/tools/msvc/mkvcbuild.pl +# +use strict; +use warnings; + +use FindBin; +use lib $FindBin::RealBin; + +use Mkvcbuild; + +chdir('../../..') if (-d '../msvc' && -d '../../../src'); +die 'Must run from root or msvc directory' + unless (-d 'src/tools/msvc' && -d 'src'); + +die 'Could not find config_default.pl' + unless (-f 'src/tools/msvc/config_default.pl'); +print "Warning: no config.pl found, using default.\n" + unless (-f 'src/tools/msvc/config.pl'); + +our $config; +do './src/tools/msvc/config_default.pl'; +do './src/tools/msvc/config.pl' if (-f 'src/tools/msvc/config.pl'); + +Mkvcbuild::mkvcbuild($config); diff --git a/src/tools/msvc/pgbison.bat b/src/tools/msvc/pgbison.bat new file mode 100755 index 0000000..dc8ac4a --- /dev/null +++ b/src/tools/msvc/pgbison.bat @@ -0,0 +1,7 @@ +@echo off + +REM src/tools/msvc/pgbison.bat +REM all the logic for this now belongs in pgbison.pl. This file really +REM only exists so you don't have to type "perl src/tools/msvc/pgbison.pl" +REM Resist any temptation to add any logic here. +@perl %~dp0/pgbison.pl %* diff --git a/src/tools/msvc/pgbison.pl b/src/tools/msvc/pgbison.pl new file mode 100644 index 0000000..e83c0b1 --- /dev/null +++ b/src/tools/msvc/pgbison.pl @@ -0,0 +1,55 @@ +# -*-perl-*- hey - emacs - this is a perl file + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# src/tools/msvc/pgbison.pl + +use strict; +use warnings; + +use File::Basename; + +# assume we are in the postgres source root + +do './src/tools/msvc/buildenv.pl' if -e 'src/tools/msvc/buildenv.pl'; + +my ($bisonver) = `bison -V`; # grab first line +$bisonver = (split(/\s+/, $bisonver))[3]; # grab version number + +unless ($bisonver eq '1.875' || $bisonver ge '2.2') +{ + print "WARNING! Bison install not found, or unsupported Bison version.\n"; + print "echo Attempting to build without.\n"; + exit 0; +} + +my $input = shift; +if ($input !~ /\.y$/) +{ + print "Input must be a .y file\n"; + exit 1; +} +elsif (!-e $input) +{ + print "Input file $input not found\n"; + exit 1; +} + +(my $output = $input) =~ s/\.y$/.c/; + +# plpgsql just has to be different +$output =~ s/gram\.c$/pl_gram.c/ if $input =~ /src.pl.plpgsql.src.gram\.y$/; + +my $makefile = dirname($input) . "/Makefile"; +my ($mf, $make); +open($mf, '<', $makefile); +local $/ = undef; +$make = <$mf>; +close($mf); +my $basetarg = basename($output); +my $headerflag = ($make =~ /^$basetarg:\s+BISONFLAGS\b.*-d/m ? '-d' : ''); + +my $nodep = $bisonver ge '3.0' ? "-Wno-deprecated" : ""; + +system("bison $nodep $headerflag $input -o $output"); +exit $? >> 8; diff --git a/src/tools/msvc/pgflex.bat b/src/tools/msvc/pgflex.bat new file mode 100755 index 0000000..f20834b --- /dev/null +++ b/src/tools/msvc/pgflex.bat @@ -0,0 +1,7 @@ +@echo off + +REM src/tools/msvc/pgflex.bat +REM all the logic for this now belongs in pgflex.pl. This file really +REM only exists so you don't have to type "perl src/tools/msvc/pgflex.pl" +REM Resist any temptation to add any logic here. +@perl %~dp0/pgflex.pl %* diff --git a/src/tools/msvc/pgflex.pl b/src/tools/msvc/pgflex.pl new file mode 100644 index 0000000..0728b85 --- /dev/null +++ b/src/tools/msvc/pgflex.pl @@ -0,0 +1,108 @@ +# -*-perl-*- hey - emacs - this is a perl file + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# src/tools/msvc/pgflex.pl + +use strict; +use warnings; + +use File::Basename; + +# silence flex bleatings about file path style +$ENV{CYGWIN} = 'nodosfilewarning'; + +# assume we are in the postgres source root + +do './src/tools/msvc/buildenv.pl' if -e 'src/tools/msvc/buildenv.pl'; + +my ($flexver) = `flex -V`; # grab first line +$flexver = (split(/\s+/, $flexver))[1]; +$flexver =~ s/[^0-9.]//g; +my @verparts = split(/\./, $flexver); +unless ($verparts[0] == 2 + && ($verparts[1] > 5 || ($verparts[1] == 5 && $verparts[2] >= 31))) +{ + print "WARNING! Flex install not found, or unsupported Flex version.\n"; + print "echo Attempting to build without.\n"; + exit 0; +} + +my $input = shift; +if ($input !~ /\.l$/) +{ + print "Input must be a .l file\n"; + exit 1; +} +elsif (!-e $input) +{ + print "Input file $input not found\n"; + exit 1; +} + +(my $output = $input) =~ s/\.l$/.c/; + +# get flex flags from make file +my $makefile = dirname($input) . "/Makefile"; +my ($mf, $make); +open($mf, '<', $makefile); +local $/ = undef; +$make = <$mf>; +close($mf); +my $basetarg = basename($output); +my $flexflags = ($make =~ /^$basetarg:\s*FLEXFLAGS\s*=\s*(\S.*)/m ? $1 : ''); + +system("flex $flexflags -o$output $input"); +if ($? == 0) +{ + + # Check for "%option reentrant" in .l file. + my $lfile; + open($lfile, '<', $input) || die "opening $input for reading: $!"; + my $lcode = <$lfile>; + close($lfile); + if ($lcode =~ /\%option\sreentrant/) + { + + # Reentrant scanners usually need a fix to prevent + # "unused variable" warnings with older flex versions. + system("perl src\\tools\\fix-old-flex-code.pl $output"); + } + else + { + + # For non-reentrant scanners we need to fix up the yywrap + # macro definition to keep the MS compiler happy. + # For reentrant scanners (like the core scanner) we do not + # need to (and must not) change the yywrap definition. + my $cfile; + open($cfile, '<', $output) || die "opening $output for reading: $!"; + my $ccode = <$cfile>; + close($cfile); + $ccode =~ s/yywrap\(n\)/yywrap()/; + open($cfile, '>', $output) || die "opening $output for writing: $!"; + print $cfile $ccode; + close($cfile); + } + if ($flexflags =~ /\s-b\s/) + { + my $lexback = "lex.backup"; + open($lfile, '<', $lexback) || die "opening $lexback for reading: $!"; + my $lexbacklines = <$lfile>; + close($lfile); + my $linecount = $lexbacklines =~ tr /\n/\n/; + if ($linecount != 1) + { + print "Scanner requires backup, see lex.backup.\n"; + exit 1; + } + unlink $lexback; + } + + exit 0; + +} +else +{ + exit $? >> 8; +} diff --git a/src/tools/msvc/vcregress.bat b/src/tools/msvc/vcregress.bat new file mode 100644 index 0000000..7fba45c --- /dev/null +++ b/src/tools/msvc/vcregress.bat @@ -0,0 +1,6 @@ +@echo off +REM src/tools/msvc/vcregress.bat +REM all the logic for this now belongs in vcregress.pl. This file really +REM only exists so you don't have to type "perl vcregress.pl" +REM Resist any temptation to add any logic here. +@perl %~dp0/vcregress.pl %* diff --git a/src/tools/msvc/vcregress.pl b/src/tools/msvc/vcregress.pl new file mode 100644 index 0000000..2b7efe2 --- /dev/null +++ b/src/tools/msvc/vcregress.pl @@ -0,0 +1,842 @@ +# -*-perl-*- hey - emacs - this is a perl file + +# Copyright (c) 2021, PostgreSQL Global Development Group + +# src/tools/msvc/vcregress.pl + +use strict; +use warnings; + +our $config; + +use Cwd; +use File::Basename; +use File::Copy; +use File::Find (); +use File::Path qw(rmtree); +use File::Spec qw(devnull); + +use FindBin; +use lib $FindBin::RealBin; + +use Install qw(Install); + +my $startdir = getcwd(); + +chdir "../../.." if (-d "../../../src/tools/msvc"); + +my $topdir = getcwd(); +my $tmp_installdir = "$topdir/tmp_install"; + +do './src/tools/msvc/config_default.pl'; +do './src/tools/msvc/config.pl' if (-f 'src/tools/msvc/config.pl'); + +my $devnull = File::Spec->devnull; + +# These values are defaults that can be overridden by the calling environment +# (see buildenv.pl processing below). We assume that the ones listed here +# always exist by default. Other values may optionally be set for bincheck +# or taptest, see set_command_env() below. +# c.f. src/Makefile.global.in and configure.ac +$ENV{TAR} ||= 'tar'; + +# buildenv.pl is for specifying the build environment settings +# it should contain lines like: +# $ENV{PATH} = "c:/path/to/bison/bin;$ENV{PATH}"; + +if (-e "src/tools/msvc/buildenv.pl") +{ + do "./src/tools/msvc/buildenv.pl"; +} + +my $what = shift || ""; +if ($what =~ + /^(check|installcheck|plcheck|contribcheck|modulescheck|ecpgcheck|isolationcheck|upgradecheck|bincheck|recoverycheck|taptest)$/i + ) +{ + $what = uc $what; +} +else +{ + usage(); +} + +# use a capital C here because config.pl has $config +my $Config = -e "release/postgres/postgres.exe" ? "Release" : "Debug"; + +copy("$Config/refint/refint.dll", "src/test/regress"); +copy("$Config/autoinc/autoinc.dll", "src/test/regress"); +copy("$Config/regress/regress.dll", "src/test/regress"); +copy("$Config/dummy_seclabel/dummy_seclabel.dll", "src/test/regress"); + +# Configuration settings used by TAP tests +$ENV{with_ssl} = $config->{openssl} ? 'openssl' : 'no'; +$ENV{with_ldap} = $config->{ldap} ? 'yes' : 'no'; +$ENV{with_icu} = $config->{icu} ? 'yes' : 'no'; +$ENV{with_gssapi} = $config->{gss} ? 'yes' : 'no'; +$ENV{with_krb_srvnam} = $config->{krb_srvnam} || 'postgres'; +$ENV{with_readline} = 'no'; + +$ENV{PATH} = "$topdir/$Config/libpq;$ENV{PATH}"; + +if ($ENV{PERL5LIB}) +{ + $ENV{PERL5LIB} = "$topdir/src/tools/msvc;$ENV{PERL5LIB}"; +} +else +{ + $ENV{PERL5LIB} = "$topdir/src/tools/msvc"; +} + +my $maxconn = ""; +$maxconn = "--max-connections=$ENV{MAX_CONNECTIONS}" + if $ENV{MAX_CONNECTIONS}; + +my $temp_config = ""; +$temp_config = "--temp-config=\"$ENV{TEMP_CONFIG}\"" + if $ENV{TEMP_CONFIG}; + +chdir "src/test/regress"; + +my %command = ( + CHECK => \&check, + PLCHECK => \&plcheck, + INSTALLCHECK => \&installcheck, + ECPGCHECK => \&ecpgcheck, + CONTRIBCHECK => \&contribcheck, + MODULESCHECK => \&modulescheck, + ISOLATIONCHECK => \&isolationcheck, + BINCHECK => \&bincheck, + RECOVERYCHECK => \&recoverycheck, + UPGRADECHECK => \&upgradecheck, + TAPTEST => \&taptest,); + +my $proc = $command{$what}; + +exit 3 unless $proc; + +&$proc(@ARGV); + +exit 0; + +######################################################################## + +# Helper function for set_command_env, to set one environment command. +sub set_single_env +{ + my $envname = shift; + my $envdefault = shift; + + # If a command is defined by the environment, just use it. + return if (defined($ENV{$envname})); + + # Nothing is defined, so attempt to assign a default. The command + # may not be in the current environment, hence check if it can be + # executed. + my $rc = system("$envdefault --version >$devnull 2>&1"); + + # Set the environment to the default if it exists, else leave it. + $ENV{$envname} = $envdefault if $rc == 0; + return; +} + +# Set environment values for various command types. These can be used +# in the TAP tests. +sub set_command_env +{ + set_single_env('GZIP_PROGRAM', 'gzip'); + set_single_env('LZ4', 'lz4'); +} + +sub installcheck_internal +{ + my ($schedule, @EXTRA_REGRESS_OPTS) = @_; + # for backwards compatibility, "serial" runs the tests in + # parallel_schedule one by one. + my $maxconn = $maxconn; + $maxconn = "--max-connections=1" if $schedule eq 'serial'; + $schedule = 'parallel' if $schedule eq 'serial'; + + my @args = ( + "../../../$Config/pg_regress/pg_regress", + "--dlpath=.", + "--bindir=../../../$Config/psql", + "--schedule=${schedule}_schedule", + "--max-concurrent-tests=20", + "--make-testtablespace-dir", + "--encoding=SQL_ASCII", + "--no-locale"); + push(@args, $maxconn) if $maxconn; + push(@args, @EXTRA_REGRESS_OPTS); + system(@args); + my $status = $? >> 8; + exit $status if $status; + return; +} + +sub installcheck +{ + my $schedule = shift || 'serial'; + installcheck_internal($schedule); + return; +} + +sub check +{ + my $schedule = shift || 'parallel'; + # for backwards compatibility, "serial" runs the tests in + # parallel_schedule one by one. + my $maxconn = $maxconn; + $maxconn = "--max-connections=1" if $schedule eq 'serial'; + $schedule = 'parallel' if $schedule eq 'serial'; + + InstallTemp(); + chdir "${topdir}/src/test/regress"; + my @args = ( + "../../../$Config/pg_regress/pg_regress", + "--dlpath=.", + "--bindir=", + "--schedule=${schedule}_schedule", + "--max-concurrent-tests=20", + "--make-testtablespace-dir", + "--encoding=SQL_ASCII", + "--no-locale", + "--temp-instance=./tmp_check"); + push(@args, $maxconn) if $maxconn; + push(@args, $temp_config) if $temp_config; + system(@args); + my $status = $? >> 8; + exit $status if $status; + return; +} + +sub ecpgcheck +{ + my $msbflags = $ENV{MSBFLAGS} || ""; + chdir $startdir; + system("msbuild ecpg_regression.proj $msbflags /p:config=$Config"); + my $status = $? >> 8; + exit $status if $status; + InstallTemp(); + chdir "$topdir/src/interfaces/ecpg/test"; + my $schedule = "ecpg"; + my @args = ( + "../../../../$Config/pg_regress_ecpg/pg_regress_ecpg", + "--bindir=", + "--dbname=ecpg1_regression,ecpg2_regression", + "--create-role=regress_ecpg_user1,regress_ecpg_user2", + "--schedule=${schedule}_schedule", + "--encoding=SQL_ASCII", + "--no-locale", + "--temp-instance=./tmp_chk"); + push(@args, $maxconn) if $maxconn; + system(@args); + $status = $? >> 8; + exit $status if $status; + return; +} + +sub isolationcheck +{ + chdir "../isolation"; + copy("../../../$Config/isolationtester/isolationtester.exe", + "../../../$Config/pg_isolation_regress"); + my @args = ( + "../../../$Config/pg_isolation_regress/pg_isolation_regress", + "--bindir=../../../$Config/psql", + "--inputdir=.", + "--schedule=./isolation_schedule"); + push(@args, $maxconn) if $maxconn; + system(@args); + my $status = $? >> 8; + exit $status if $status; + return; +} + +sub tap_check +{ + die "Tap tests not enabled in configuration" + unless $config->{tap_tests}; + + my @flags; + foreach my $arg (0 .. scalar(@_) - 1) + { + next unless $_[$arg] =~ /^PROVE_FLAGS=(.*)/; + @flags = split(/\s+/, $1); + splice(@_, $arg, 1); + last; + } + + my $dir = shift; + chdir $dir; + + # Fetch and adjust PROVE_TESTS, applying glob() to each element + # defined to build a list of all the tests matching patterns. + my $prove_tests_val = $ENV{PROVE_TESTS} || "t/*.pl"; + my @prove_tests_array = split(/\s+/, $prove_tests_val); + my @prove_tests = (); + foreach (@prove_tests_array) + { + push(@prove_tests, glob($_)); + } + + # Fetch and adjust PROVE_FLAGS, handling multiple arguments. + my $prove_flags_val = $ENV{PROVE_FLAGS} || ""; + my @prove_flags = split(/\s+/, $prove_flags_val); + + my @args = ("prove", @flags, @prove_tests, @prove_flags); + + # adjust the environment for just this test + local %ENV = %ENV; + $ENV{PERL5LIB} = "$topdir/src/test/perl;$ENV{PERL5LIB}"; + $ENV{PG_REGRESS} = "$topdir/$Config/pg_regress/pg_regress"; + $ENV{REGRESS_SHLIB} = "$topdir/src/test/regress/regress.dll"; + + $ENV{TESTDIR} = "$dir"; + my $module = basename $dir; + # add the module build dir as the second element in the PATH + $ENV{PATH} =~ s!;!;$topdir/$Config/$module;!; + + rmtree('tmp_check'); + system(@args); + my $status = $? >> 8; + return $status; +} + +sub bincheck +{ + InstallTemp(); + + set_command_env(); + + my $mstat = 0; + + # Find out all the existing TAP tests by looking for t/ directories + # in the tree. + my @bin_dirs = glob("$topdir/src/bin/*"); + + # Process each test + foreach my $dir (@bin_dirs) + { + next unless -d "$dir/t"; + my $status = tap_check($dir); + $mstat ||= $status; + } + exit $mstat if $mstat; + return; +} + +sub taptest +{ + my $dir = shift; + my @args; + + if ($dir =~ /^PROVE_FLAGS=/) + { + push(@args, $dir); + $dir = shift; + } + + die "no tests found!" unless -d "$topdir/$dir/t"; + + push(@args, "$topdir/$dir"); + + InstallTemp(); + + set_command_env(); + + my $status = tap_check(@args); + exit $status if $status; + return; +} + +sub mangle_plpython3 +{ + my $tests = shift; + mkdir "results" unless -d "results"; + mkdir "sql/python3"; + mkdir "results/python3"; + mkdir "expected/python3"; + + foreach my $test (@$tests) + { + local $/ = undef; + foreach my $dir ('sql', 'expected') + { + my $extension = ($dir eq 'sql' ? 'sql' : 'out'); + + my @files = + glob("$dir/$test.$extension $dir/${test}_[0-9].$extension"); + foreach my $file (@files) + { + open(my $handle, '<', $file) + || die "test file $file not found"; + my $contents = <$handle>; + close($handle); + do + { + s/<type 'exceptions\.([[:alpha:]]*)'>/<class '$1'>/g; + s/<type 'long'>/<class 'int'>/g; + s/([0-9][0-9]*)L/$1/g; + s/([ [{])u"/$1"/g; + s/([ [{])u'/$1'/g; + s/def next/def __next__/g; + s/LANGUAGE plpython2?u/LANGUAGE plpython3u/g; + s/EXTENSION (\S*?)plpython2?u/EXTENSION $1plpython3u/g; + s/installing required extension "plpython2u"/installing required extension "plpython3u"/g; + } + for ($contents); + my $base = basename $file; + open($handle, '>', "$dir/python3/$base") + || die "opening python 3 file for $file"; + print $handle $contents; + close($handle); + } + } + } + do { s!^!python3/!; } + foreach (@$tests); + return @$tests; +} + +sub plcheck +{ + chdir "$topdir/src/pl"; + + foreach my $dir (glob("*/src *")) + { + next unless -d "$dir/sql" && -d "$dir/expected"; + my $lang; + if ($dir eq 'plpgsql/src') + { + $lang = 'plpgsql'; + } + elsif ($dir eq 'tcl') + { + $lang = 'pltcl'; + } + else + { + $lang = $dir; + } + if ($lang eq 'plpython') + { + next + unless -d "$topdir/$Config/plpython2" + || -d "$topdir/$Config/plpython3"; + $lang = 'plpythonu'; + } + else + { + next unless -d "$topdir/$Config/$lang"; + } + my @lang_args = ("--load-extension=$lang"); + chdir $dir; + my @tests = fetchTests(); + @tests = mangle_plpython3(\@tests) + if $lang eq 'plpythonu' && -d "$topdir/$Config/plpython3"; + if ($lang eq 'plperl') + { + + # plperl tests will install the extensions themselves + @lang_args = (); + + # assume we're using this perl to built postgres + # test if we can run two interpreters in one backend, and if so + # run the trusted/untrusted interaction tests + use Config; + if ($Config{usemultiplicity} eq 'define') + { + push(@tests, 'plperl_plperlu'); + } + } + elsif ($lang eq 'plpythonu' && -d "$topdir/$Config/plpython3") + { + @lang_args = (); + } + + # Move on if no tests are listed. + next if (scalar @tests == 0); + + print + "============================================================\n"; + print "Checking $lang\n"; + my @args = ( + "$topdir/$Config/pg_regress/pg_regress", + "--bindir=$topdir/$Config/psql", + "--dbname=pl_regression", @lang_args, @tests); + system(@args); + my $status = $? >> 8; + exit $status if $status; + chdir "$topdir/src/pl"; + } + + chdir "$topdir"; + return; +} + +sub subdircheck +{ + my $module = shift; + + if ( !-d "$module/sql" + || !-d "$module/expected" + || (!-f "$module/GNUmakefile" && !-f "$module/Makefile")) + { + return; + } + + chdir $module; + my @tests = fetchTests(); + + # Leave if no tests are listed in the module. + if (scalar @tests == 0) + { + chdir ".."; + return; + } + + my @opts = fetchRegressOpts(); + + # Special processing for python transform modules, see their respective + # Makefiles for more details regarding Python-version specific + # dependencies. + if ($module =~ /_plpython$/) + { + die "Python not enabled in configuration" + if !defined($config->{python}); + + @opts = grep { $_ !~ /plpythonu/ } @opts; + + if (-d "$topdir/$Config/plpython2") + { + push @opts, "--load-extension=plpythonu"; + push @opts, '--load-extension=' . $module . 'u'; + } + else + { + # must be python 3 + @tests = mangle_plpython3(\@tests); + } + } + + print "============================================================\n"; + print "Checking $module\n"; + my @args = ( + "$topdir/$Config/pg_regress/pg_regress", + "--bindir=${topdir}/${Config}/psql", + "--dbname=contrib_regression", @opts, @tests); + print join(' ', @args), "\n"; + system(@args); + chdir ".."; + return; +} + +sub contribcheck +{ + chdir "../../../contrib"; + my $mstat = 0; + foreach my $module (glob("*")) + { + # these configuration-based exclusions must match Install.pm + next if ($module eq "uuid-ossp" && !defined($config->{uuid})); + next if ($module eq "sslinfo" && !defined($config->{openssl})); + next if ($module eq "xml2" && !defined($config->{xml})); + next if ($module =~ /_plperl$/ && !defined($config->{perl})); + next if ($module =~ /_plpython$/ && !defined($config->{python})); + next if ($module eq "sepgsql"); + + subdircheck($module); + my $status = $? >> 8; + $mstat ||= $status; + } + exit $mstat if $mstat; + return; +} + +sub modulescheck +{ + chdir "../../../src/test/modules"; + my $mstat = 0; + foreach my $module (glob("*")) + { + subdircheck($module); + my $status = $? >> 8; + $mstat ||= $status; + } + exit $mstat if $mstat; + return; +} + +sub recoverycheck +{ + InstallTemp(); + + my $mstat = 0; + my $dir = "$topdir/src/test/recovery"; + my $status = tap_check($dir); + exit $status if $status; + return; +} + +# Run "initdb", then reconfigure authentication. +sub standard_initdb +{ + return ( + system('initdb', '-N') == 0 and system( + "$topdir/$Config/pg_regress/pg_regress", '--config-auth', + $ENV{PGDATA}) == 0); +} + +# This is similar to appendShellString(). Perl system(@args) bypasses +# cmd.exe, so omit the caret escape layer. +sub quote_system_arg +{ + my $arg = shift; + + # Change N >= 0 backslashes before a double quote to 2N+1 backslashes. + $arg =~ s/(\\*)"/${\($1 . $1)}\\"/gs; + + # Change N >= 1 backslashes at end of argument to 2N backslashes. + $arg =~ s/(\\+)$/${\($1 . $1)}/gs; + + # Wrap the whole thing in unescaped double quotes. + return "\"$arg\""; +} + +# Generate a database with a name made of a range of ASCII characters, useful +# for testing pg_upgrade. +sub generate_db +{ + my ($prefix, $from_char, $to_char, $suffix) = @_; + + my $dbname = $prefix; + for my $i ($from_char .. $to_char) + { + next if $i == 7 || $i == 10 || $i == 13; # skip BEL, LF, and CR + $dbname = $dbname . sprintf('%c', $i); + } + $dbname .= $suffix; + + system('createdb', quote_system_arg($dbname)); + my $status = $? >> 8; + exit $status if $status; + return; +} + +sub upgradecheck +{ + my $status; + my $cwd = getcwd(); + + # Much of this comes from the pg_upgrade test.sh script, + # but it only covers the --install case, and not the case + # where the old and new source or bin dirs are different. + # i.e. only this version to this version check. That's + # what pg_upgrade's "make check" does. + + $ENV{PGHOST} = 'localhost'; + $ENV{PGPORT} ||= 50432; + my $tmp_root = "$topdir/src/bin/pg_upgrade/tmp_check"; + rmtree($tmp_root); + mkdir $tmp_root || die $!; + my $upg_tmp_install = "$tmp_root/install"; # unshared temp install + print "Setting up temp install\n\n"; + Install($upg_tmp_install, "all", $config); + + # Install does a chdir, so change back after that + chdir $cwd; + my ($bindir, $libdir, $oldsrc, $newsrc) = + ("$upg_tmp_install/bin", "$upg_tmp_install/lib", $topdir, $topdir); + $ENV{PATH} = "$bindir;$ENV{PATH}"; + my $data = "$tmp_root/data"; + $ENV{PGDATA} = "$data.old"; + my $outputdir = "$tmp_root/regress"; + my @EXTRA_REGRESS_OPTS = ("--outputdir=$outputdir"); + mkdir "$outputdir" || die $!; + + my $logdir = "$topdir/src/bin/pg_upgrade/log"; + rmtree($logdir); + mkdir $logdir || die $!; + print "\nRunning initdb on old cluster\n\n"; + standard_initdb() or exit 1; + print "\nStarting old cluster\n\n"; + my @args = ('pg_ctl', 'start', '-l', "$logdir/postmaster1.log"); + system(@args) == 0 or exit 1; + + print "\nCreating databases with names covering most ASCII bytes\n\n"; + generate_db("\\\"\\", 1, 45, "\\\\\"\\\\\\"); + generate_db('', 46, 90, ''); + generate_db('', 91, 127, ''); + + print "\nSetting up data for upgrading\n\n"; + installcheck_internal('parallel', @EXTRA_REGRESS_OPTS); + + # now we can chdir into the source dir + chdir "$topdir/src/bin/pg_upgrade"; + print "\nDumping old cluster\n\n"; + @args = ('pg_dumpall', '-f', "$tmp_root/dump1.sql"); + system(@args) == 0 or exit 1; + print "\nStopping old cluster\n\n"; + system("pg_ctl stop") == 0 or exit 1; + $ENV{PGDATA} = "$data"; + print "\nSetting up new cluster\n\n"; + standard_initdb() or exit 1; + print "\nRunning pg_upgrade\n\n"; + @args = ('pg_upgrade', '-d', "$data.old", '-D', $data, '-b', $bindir); + system(@args) == 0 or exit 1; + print "\nStarting new cluster\n\n"; + @args = ('pg_ctl', '-l', "$logdir/postmaster2.log", 'start'); + system(@args) == 0 or exit 1; + print "\nDumping new cluster\n\n"; + @args = ('pg_dumpall', '-f', "$tmp_root/dump2.sql"); + system(@args) == 0 or exit 1; + print "\nStopping new cluster\n\n"; + system("pg_ctl stop") == 0 or exit 1; + print "\nDeleting old cluster\n\n"; + system(".\\delete_old_cluster.bat") == 0 or exit 1; + print "\nComparing old and new cluster dumps\n\n"; + + @args = ('diff', '-q', "$tmp_root/dump1.sql", "$tmp_root/dump2.sql"); + system(@args); + $status = $?; + if (!$status) + { + print "PASSED\n"; + } + else + { + print "dumps not identical!\n"; + exit(1); + } + return; +} + +sub fetchRegressOpts +{ + my $handle; + open($handle, '<', "GNUmakefile") + || open($handle, '<', "Makefile") + || die "Could not open Makefile"; + local ($/) = undef; + my $m = <$handle>; + close($handle); + my @opts; + + $m =~ s{\\\r?\n}{}g; + if ($m =~ /^\s*REGRESS_OPTS\s*\+?=(.*)/m) + { + + # Substitute known Makefile variables, then ignore options that retain + # an unhandled variable reference. Ignore anything that isn't an + # option starting with "--". + @opts = grep { !/\$\(/ && /^--/ } + map { (my $x = $_) =~ s/\Q$(top_builddir)\E/\"$topdir\"/; $x; } + split(/\s+/, $1); + } + if ($m =~ /^\s*ENCODING\s*=\s*(\S+)/m) + { + push @opts, "--encoding=$1"; + } + if ($m =~ /^\s*NO_LOCALE\s*=\s*\S+/m) + { + push @opts, "--no-locale"; + } + return @opts; +} + +# Fetch the list of tests by parsing a module's Makefile. An empty +# list is returned if the module does not need to run anything. +sub fetchTests +{ + + my $handle; + open($handle, '<', "GNUmakefile") + || open($handle, '<', "Makefile") + || die "Could not open Makefile"; + local ($/) = undef; + my $m = <$handle>; + close($handle); + my $t = ""; + + $m =~ s{\\\r?\n}{}g; + + # A module specifying NO_INSTALLCHECK does not support installcheck, + # so bypass its run by returning an empty set of tests. + if ($m =~ /^\s*NO_INSTALLCHECK\s*=\s*\S+/m) + { + return (); + } + + if ($m =~ /^REGRESS\s*=\s*(.*)$/gm) + { + $t = $1; + $t =~ s/\s+/ /g; + + if ($m =~ /contrib\/pgcrypto/) + { + + # pgcrypto is special since the tests depend on the + # configuration of the build + + my $cftests = + $config->{openssl} + ? GetTests("OSSL_TESTS", $m) + : GetTests("INT_TESTS", $m); + my $pgptests = + $config->{zlib} + ? GetTests("ZLIB_TST", $m) + : GetTests("ZLIB_OFF_TST", $m); + $t =~ s/\$\(CF_TESTS\)/$cftests/; + $t =~ s/\$\(CF_PGP_TESTS\)/$pgptests/; + } + } + + return split(/\s+/, $t); +} + +sub GetTests +{ + my $testname = shift; + my $m = shift; + if ($m =~ /^$testname\s*=\s*(.*)$/gm) + { + return $1; + } + return ""; +} + +sub InstallTemp +{ + unless ($ENV{NO_TEMP_INSTALL}) + { + print "Setting up temp install\n\n"; + Install("$tmp_installdir", "all", $config); + } + $ENV{PATH} = "$tmp_installdir/bin;$ENV{PATH}"; + return; +} + +sub usage +{ + print STDERR + "Usage: vcregress.pl <mode> [ <arg>]\n\n", + "Options for <mode>:\n", + " bincheck run tests of utilities in src/bin/\n", + " check deploy instance and run regression tests on it\n", + " contribcheck run tests of modules in contrib/\n", + " ecpgcheck run regression tests of ECPG\n", + " installcheck run regression tests on existing instance\n", + " isolationcheck run isolation tests\n", + " modulescheck run tests of modules in src/test/modules/\n", + " plcheck run tests of PL languages\n", + " recoverycheck run recovery test suite\n", + " taptest run an arbitrary TAP test set\n", + " upgradecheck run tests of pg_upgrade\n", + "\nOptions for <arg>: (used by check and installcheck)\n", + " serial serial mode\n", + " parallel parallel mode\n", + "\nOption for <arg>: for taptest\n", + " TEST_DIR (required) directory where tests reside\n"; + exit(1); +} |