summaryrefslogtreecommitdiffstats
path: root/src/tools/msvc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/msvc/.gitignore3
-rw-r--r--src/tools/msvc/Install.pm777
-rw-r--r--src/tools/msvc/MSBuildProject.pm536
-rw-r--r--src/tools/msvc/Mkvcbuild.pm1133
-rw-r--r--src/tools/msvc/Project.pm448
-rw-r--r--src/tools/msvc/README99
-rw-r--r--src/tools/msvc/Solution.pm1359
-rw-r--r--src/tools/msvc/VSObjectFactory.pm182
-rwxr-xr-xsrc/tools/msvc/build.bat6
-rw-r--r--src/tools/msvc/build.pl92
-rwxr-xr-xsrc/tools/msvc/clean.bat142
-rw-r--r--src/tools/msvc/config_default.pl31
-rw-r--r--src/tools/msvc/dummylib/README13
-rw-r--r--src/tools/msvc/dummylib/Win32.pm7
-rw-r--r--src/tools/msvc/dummylib/Win32/Registry.pm16
-rw-r--r--src/tools/msvc/dummylib/Win32API/File.pm17
-rw-r--r--src/tools/msvc/ecpg_regression.proj64
-rw-r--r--src/tools/msvc/gendef.pl181
-rw-r--r--src/tools/msvc/install.bat6
-rwxr-xr-xsrc/tools/msvc/install.pl39
-rw-r--r--src/tools/msvc/mkvcbuild.pl31
-rwxr-xr-xsrc/tools/msvc/pgbison.bat7
-rw-r--r--src/tools/msvc/pgbison.pl55
-rwxr-xr-xsrc/tools/msvc/pgflex.bat7
-rw-r--r--src/tools/msvc/pgflex.pl108
-rw-r--r--src/tools/msvc/vcregress.bat6
-rw-r--r--src/tools/msvc/vcregress.pl842
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 = '&quot;' . $lib . "&quot;";
+ }
+
+ 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/"/&quot;&quot;/g;
+ $self->{defines} .= $def . ';';
+ return;
+}
+
+sub FullExportDLL
+{
+ my ($self, $libname) = @_;
+
+ $self->{builddef} = 1;
+ $self->{def} = "./__CFGNAME__/$self->{name}/$self->{name}.def";
+ $self->{implib} = "__CFGNAME__/$self->{name}/$libname";
+ return;
+}
+
+sub UseDef
+{
+ my ($self, $def) = @_;
+
+ $self->{def} = $def;
+ return;
+}
+
+sub AddDir
+{
+ my ($self, $reldir) = @_;
+ my $mf = read_makefile($reldir);
+
+ $mf =~ s{\\\r?\n}{}g;
+ if ($mf =~ m{^(?:SUB)?DIRS[^=]*=\s*(.*)$}mg)
+ {
+ foreach my $subdir (split /\s+/, $1)
+ {
+ next
+ if $subdir eq "\$(top_builddir)/src/timezone"
+ ; #special case for non-standard include
+ next
+ if $reldir . "/" . $subdir eq "src/backend/port/darwin";
+
+ $self->AddDir($reldir . "/" . $subdir);
+ }
+ }
+ while ($mf =~ m{^(?:EXTRA_)?OBJS[^=]*=\s*(.*)$}m)
+ {
+ my $s = $1;
+ my $filter_re = qr{\$\(filter ([^,]+),\s+\$\(([^\)]+)\)\)};
+ while ($s =~ /$filter_re/)
+ {
+
+ # Process $(filter a b c, $(VAR)) expressions
+ my $list = $1;
+ my $filter = $2;
+ $list =~ s/\.o/\.c/g;
+ my @pieces = split /\s+/, $list;
+ my $matches = "";
+ foreach my $p (@pieces)
+ {
+
+ if ($filter eq "LIBOBJS")
+ {
+ no warnings qw(once);
+ if (grep(/$p/, @main::pgportfiles) == 1)
+ {
+ $p =~ s/\.c/\.o/;
+ $matches .= $p . " ";
+ }
+ }
+ else
+ {
+ confess "Unknown filter $filter\n";
+ }
+ }
+ $s =~ s/$filter_re/$matches/;
+ }
+ foreach my $f (split /\s+/, $s)
+ {
+ next if $f =~ /^\s*$/;
+ next if $f eq "\\";
+ next if $f =~ /\/SUBSYS.o$/;
+ $f =~ s/,$//
+ ; # Remove trailing comma that can show up from filter stuff
+ next unless $f =~ /.*\.o$/;
+ $f =~ s/\.o$/\.c/;
+ if ($f =~ /^\$\(top_builddir\)\/(.*)/)
+ {
+ $f = $1;
+ $self->{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);
+}