1package Install;
2
3#
4# Package that provides 'make install' functionality for msvc builds
5#
6# src/tools/msvc/Install.pm
7#
8use strict;
9use warnings;
10use Carp;
11use File::Basename;
12use File::Copy;
13use File::Find ();
14
15use Exporter;
16our (@ISA, @EXPORT_OK);
17@ISA       = qw(Exporter);
18@EXPORT_OK = qw(Install);
19
20my $insttype;
21my @client_contribs = ('oid2name', 'pgbench', 'vacuumlo');
22my @client_program_files = (
23	'clusterdb',     'createdb',       'createlang', 'createuser',
24	'dropdb',        'droplang',       'dropuser',   'ecpg',
25	'libecpg',       'libecpg_compat', 'libpgtypes', 'libpq',
26	'pg_basebackup', 'pg_config',      'pg_dump',    'pg_dumpall',
27	'pg_isready',    'pg_receivexlog', 'pg_recvlogical', 'pg_restore',
28	'psql', 'reindexdb',     'vacuumdb',       @client_contribs);
29
30sub lcopy
31{
32	my $src    = shift;
33	my $target = shift;
34
35	if (-f $target)
36	{
37		unlink $target || confess "Could not delete $target\n";
38	}
39
40	copy($src, $target)
41	  || confess "Could not copy $src to $target\n";
42
43}
44
45sub Install
46{
47	$| = 1;
48
49	my $target = shift;
50	$insttype = shift;
51	$insttype = "all" unless ($insttype);
52
53	# if called from vcregress, the config will be passed to us
54	# so no need to re-include these
55	our $config = shift;
56	unless ($config)
57	{
58
59		# suppress warning about harmless redeclaration of $config
60		no warnings 'misc';
61		do "./config_default.pl";
62		do "./config.pl" if (-f "config.pl");
63	}
64
65	# Move to the root path depending on the current location.
66	if (-f "../../../configure")
67	{
68		chdir("../../..");
69	}
70	elsif (-f "../../../../configure")
71	{
72		chdir("../../../..");
73	}
74
75	my $conf = "";
76	if (-d "debug")
77	{
78		$conf = "debug";
79	}
80	if (-d "release")
81	{
82		$conf = "release";
83	}
84	die "Could not find debug or release binaries" if ($conf eq "");
85	my $majorver = DetermineMajorVersion();
86	print "Installing version $majorver for $conf in $target\n";
87
88	my @client_dirs = ('bin', 'lib', 'share', 'symbols');
89	my @all_dirs = (
90		@client_dirs, 'doc', 'doc/contrib', 'doc/extension', 'share/contrib',
91		'share/extension', 'share/timezonesets', 'share/tsearch_data');
92	if ($insttype eq "client")
93	{
94		EnsureDirectories($target, @client_dirs);
95	}
96	else
97	{
98		EnsureDirectories($target, @all_dirs);
99	}
100
101	CopySolutionOutput($conf, $target);
102	my $sample_files = [];
103	my @top_dir      = ("src");
104	@top_dir = ("src\\bin", "src\\interfaces") if ($insttype eq "client");
105	File::Find::find(
106		{   wanted => sub {
107				/^.*\.sample\z/s
108				  && push(@$sample_files, $File::Find::name);
109
110				# Don't find files of in-tree temporary installations.
111				$_ eq 'share' and $File::Find::prune = 1;
112			  }
113		},
114		@top_dir);
115	CopySetOfFiles('config files', $sample_files, $target . '/share/');
116	CopyFiles(
117		'Import libraries',
118		$target . '/lib/',
119		"$conf\\", "postgres\\postgres.lib", "libpgcommon\\libpgcommon.lib",
120		"libpgport\\libpgport.lib");
121	CopyContribFiles($config, $target);
122	CopyIncludeFiles($target);
123
124	if ($insttype ne "client")
125	{
126		CopySetOfFiles(
127			'timezone names',
128			[ glob('src\timezone\tznames\*.txt') ],
129			$target . '/share/timezonesets/');
130		CopyFiles(
131			'timezone sets',
132			$target . '/share/timezonesets/',
133			'src/timezone/tznames/', 'Default', 'Australia', 'India');
134		CopySetOfFiles(
135			'BKI files',
136			[ glob("src\\backend\\catalog\\postgres.*") ],
137			$target . '/share/');
138		CopySetOfFiles(
139			'SQL files',
140			[ glob("src\\backend\\catalog\\*.sql") ],
141			$target . '/share/');
142		CopyFiles(
143			'Information schema data', $target . '/share/',
144			'src/backend/catalog/',    'sql_features.txt');
145		GenerateConversionScript($target);
146		GenerateTimezoneFiles($target, $conf);
147		GenerateTsearchFiles($target);
148		CopySetOfFiles(
149			'Stopword files',
150			[ glob("src\\backend\\snowball\\stopwords\\*.stop") ],
151			$target . '/share/tsearch_data/');
152		CopySetOfFiles(
153			'Dictionaries sample files',
154			[ glob("src\\backend\\tsearch\\dicts\\*_sample*") ],
155			$target . '/share/tsearch_data/');
156
157		my $pl_extension_files = [];
158		my @pldirs             = ('src/pl/plpgsql/src');
159		push @pldirs, "src/pl/plperl"   if $config->{perl};
160		push @pldirs, "src/pl/plpython" if $config->{python};
161		push @pldirs, "src/pl/tcl"      if $config->{tcl};
162		File::Find::find(
163			{   wanted => sub {
164					/^(.*--.*\.sql|.*\.control)\z/s
165					  && push(@$pl_extension_files, $File::Find::name);
166
167					# Don't find files of in-tree temporary installations.
168					$_ eq 'share' and $File::Find::prune = 1;
169				  }
170			},
171			@pldirs);
172		CopySetOfFiles('PL Extension files',
173			$pl_extension_files, $target . '/share/extension/');
174	}
175
176	GenerateNLSFiles($target, $config->{nls}, $majorver) if ($config->{nls});
177
178	print "Installation complete.\n";
179}
180
181sub EnsureDirectories
182{
183	my $target = shift;
184	mkdir $target unless -d ($target);
185	while (my $d = shift)
186	{
187		mkdir $target . '/' . $d unless -d ($target . '/' . $d);
188	}
189}
190
191sub CopyFiles
192{
193	my $what    = shift;
194	my $target  = shift;
195	my $basedir = shift;
196
197	print "Copying $what";
198	while (my $f = shift)
199	{
200		print ".";
201		$f = $basedir . $f;
202		die "No file $f\n" if (!-f $f);
203		lcopy($f, $target . basename($f));
204	}
205	print "\n";
206}
207
208sub CopySetOfFiles
209{
210	my $what   = shift;
211	my $flist  = shift;
212	my $target = shift;
213	print "Copying $what" if $what;
214	foreach (@$flist)
215	{
216		my $tgt = $target . basename($_);
217		print ".";
218		lcopy($_, $tgt) || croak "Could not copy $_: $!\n";
219	}
220	print "\n";
221}
222
223sub CopySolutionOutput
224{
225	my $conf   = shift;
226	my $target = shift;
227	my $rem =
228	  qr{Project\("\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942\}"\) = "([^"]+)"};
229
230	my $sln = read_file("pgsql.sln") || croak "Could not open pgsql.sln\n";
231
232	my $vcproj = 'vcproj';
233	if ($sln =~
234		/Microsoft Visual Studio Solution File, Format Version (\d+)\.\d+/
235		&& $1 >= 11)
236	{
237		$vcproj = 'vcxproj';
238	}
239
240	print "Copying build output files...";
241	while ($sln =~ $rem)
242	{
243		my $pf = $1;
244
245		# Hash-of-arrays listing where to install things.  For each
246		# subdirectory there's a hash key, and the value is an array
247		# of file extensions to install in that subdirectory.  Example:
248		# { 'bin' => [ 'dll', 'lib' ],
249		#   'lib' => [ 'lib' ] }
250		my %install_list;
251		my $is_sharedlib = 0;
252
253		$sln =~ s/$rem//;
254
255		next
256		  if ($insttype eq "client" && !grep { $_ eq $pf }
257			@client_program_files);
258
259		my $proj = read_file("$pf.$vcproj")
260		  || croak "Could not open $pf.$vcproj\n";
261
262		# Check if this project uses a shared library by looking if
263		# SO_MAJOR_VERSION is defined in its Makefile, whose path
264		# can be found using the resource file of this project.
265		if ((      $vcproj eq 'vcxproj'
266				&& $proj =~ qr{ResourceCompile\s*Include="([^"]+)"})
267			|| (   $vcproj eq 'vcproj'
268				&& $proj =~ qr{File\s*RelativePath="([^\"]+)\.rc"}))
269		{
270			my $projpath = dirname($1);
271			my $mfname =
272			  -e "$projpath/GNUmakefile"
273			  ? "$projpath/GNUmakefile"
274			  : "$projpath/Makefile";
275			my $mf = read_file($mfname) || croak "Could not open $mfname\n";
276
277			$is_sharedlib = 1 if ($mf =~ /^SO_MAJOR_VERSION\s*=\s*(.*)$/mg);
278		}
279
280		if ($vcproj eq 'vcproj' && $proj =~ qr{ConfigurationType="([^"]+)"})
281		{
282			if ($1 == 1)
283			{
284				push(@{ $install_list{'bin'} }, "exe");
285			}
286			elsif ($1 == 2)
287			{
288				push(@{ $install_list{'lib'} }, "dll");
289				if ($is_sharedlib)
290				{
291					push(@{ $install_list{'bin'} }, "dll");
292					push(@{ $install_list{'lib'} }, "lib");
293				}
294			}
295			else
296			{
297
298				# Static libraries, such as libpgport, only used internally
299				# during build, don't install.
300				next;
301			}
302		}
303		elsif ($vcproj eq 'vcxproj'
304			&& $proj =~ qr{<ConfigurationType>(\w+)</ConfigurationType>})
305		{
306			if ($1 eq 'Application')
307			{
308				push(@{ $install_list{'bin'} }, "exe");
309			}
310			elsif ($1 eq 'DynamicLibrary')
311			{
312				push(@{ $install_list{'lib'} }, "dll");
313				if ($is_sharedlib)
314				{
315					push(@{ $install_list{'bin'} }, "dll");
316					push(@{ $install_list{'lib'} }, "lib");
317				}
318			}
319			else    # 'StaticLibrary'
320			{
321
322				# Static lib, such as libpgport, only used internally
323				# during build, don't install.
324				next;
325			}
326		}
327		else
328		{
329			croak "Could not parse $pf.$vcproj\n";
330		}
331
332		# Install each element
333		foreach my $dir (keys %install_list)
334		{
335			foreach my $ext (@{ $install_list{$dir} })
336			{
337				lcopy("$conf\\$pf\\$pf.$ext", "$target\\$dir\\$pf.$ext")
338				  || croak "Could not copy $pf.$ext\n";
339			}
340		}
341		lcopy("$conf\\$pf\\$pf.pdb", "$target\\symbols\\$pf.pdb")
342		  || croak "Could not copy $pf.pdb\n";
343		print ".";
344	}
345	print "\n";
346}
347
348sub GenerateConversionScript
349{
350	my $target = shift;
351	my $sql    = "";
352	my $F;
353
354	print "Generating conversion proc script...";
355	my $mf = read_file('src/backend/utils/mb/conversion_procs/Makefile');
356	$mf =~ s{\\\r?\n}{}g;
357	$mf =~ /^CONVERSIONS\s*=\s*(.*)$/m
358	  || die "Could not find CONVERSIONS line in conversions Makefile\n";
359	my @pieces = split /\s+/, $1;
360	while ($#pieces > 0)
361	{
362		my $name = shift @pieces;
363		my $se   = shift @pieces;
364		my $de   = shift @pieces;
365		my $func = shift @pieces;
366		my $obj  = shift @pieces;
367		$sql .= "-- $se --> $de\n";
368		$sql .=
369"CREATE OR REPLACE FUNCTION $func (INTEGER, INTEGER, CSTRING, INTERNAL, INTEGER) RETURNS VOID AS '\$libdir/$obj', '$func' LANGUAGE C STRICT;\n";
370		$sql .=
371"COMMENT ON FUNCTION $func(INTEGER, INTEGER, CSTRING, INTERNAL, INTEGER) IS 'internal conversion function for $se to $de';\n";
372		$sql .= "DROP CONVERSION pg_catalog.$name;\n";
373		$sql .=
374"CREATE DEFAULT CONVERSION pg_catalog.$name FOR '$se' TO '$de' FROM $func;\n";
375		$sql .=
376"COMMENT ON CONVERSION pg_catalog.$name IS 'conversion for $se to $de';\n\n";
377	}
378	open($F, ">$target/share/conversion_create.sql")
379	  || die "Could not write to conversion_create.sql\n";
380	print $F $sql;
381	close($F);
382	print "\n";
383}
384
385sub GenerateTimezoneFiles
386{
387	my $target = shift;
388	my $conf   = shift;
389	my $mf     = read_file("src/timezone/Makefile");
390	$mf =~ s{\\\r?\n}{}g;
391
392	$mf =~ /^TZDATAFILES\s*:?=\s*(.*)$/m
393	  || die "Could not find TZDATAFILES line in timezone makefile\n";
394	my @tzfiles = split /\s+/, $1;
395
396	$mf =~ /^POSIXRULES\s*:?=\s*(.*)$/m
397	  || die "Could not find POSIXRULES line in timezone makefile\n";
398	my $posixrules = $1;
399	$posixrules =~ s/\s+//g;
400
401	print "Generating timezone files...";
402
403	my @args = ("$conf/zic/zic", '-d', "$target/share/timezone",
404				'-p', "$posixrules", '-b', 'fat');
405	foreach (@tzfiles)
406	{
407		my $tzfile = $_;
408		$tzfile =~ s|\$\(srcdir\)|src/timezone|;
409		push(@args, $tzfile);
410	}
411
412	system(@args);
413	print "\n";
414}
415
416sub GenerateTsearchFiles
417{
418	my $target = shift;
419
420	print "Generating tsearch script...";
421	my $F;
422	my $tmpl = read_file('src/backend/snowball/snowball.sql.in');
423	my $mf   = read_file('src/backend/snowball/Makefile');
424	$mf =~ s{\\\r?\n}{}g;
425	$mf =~ /^LANGUAGES\s*=\s*(.*)$/m
426	  || die "Could not find LANGUAGES line in snowball Makefile\n";
427	my @pieces = split /\s+/, $1;
428	open($F, ">$target/share/snowball_create.sql")
429	  || die "Could not write snowball_create.sql";
430	print $F read_file('src/backend/snowball/snowball_func.sql.in');
431
432	while ($#pieces > 0)
433	{
434		my $lang    = shift @pieces || last;
435		my $asclang = shift @pieces || last;
436		my $txt     = $tmpl;
437		my $stop    = '';
438
439		if (-s "src/backend/snowball/stopwords/$lang.stop")
440		{
441			$stop = ", StopWords=$lang";
442		}
443
444		$txt =~ s#_LANGNAME_#${lang}#gs;
445		$txt =~ s#_DICTNAME_#${lang}_stem#gs;
446		$txt =~ s#_CFGNAME_#${lang}#gs;
447		$txt =~ s#_ASCDICTNAME_#${asclang}_stem#gs;
448		$txt =~ s#_NONASCDICTNAME_#${lang}_stem#gs;
449		$txt =~ s#_STOPWORDS_#$stop#gs;
450		print $F $txt;
451		print ".";
452	}
453	close($F);
454	print "\n";
455}
456
457sub CopyContribFiles
458{
459	my $config = shift;
460	my $target = shift;
461
462	print "Copying contrib data files...";
463	foreach my $subdir ('contrib', 'src/test/modules')
464	{
465		my $D;
466		opendir($D, $subdir) || croak "Could not opendir on $subdir!\n";
467		while (my $d = readdir($D))
468		{
469			# These configuration-based exclusions must match vcregress.pl
470			next if ($d eq "uuid-ossp"       && !defined($config->{uuid}));
471			next if ($d eq "sslinfo"         && !defined($config->{openssl}));
472			next if ($d eq "xml2"            && !defined($config->{xml}));
473			next if ($d =~ /_plperl$/        && !defined($config->{perl}));
474			next if ($d =~ /_plpython$/      && !defined($config->{python}));
475			next if ($d eq "sepgsql");
476
477			CopySubdirFiles($subdir, $d, $config, $target);
478		}
479	}
480	print "\n";
481}
482
483sub CopySubdirFiles
484{
485	my $subdir = shift;
486	my $module = shift;
487	my $config = shift;
488	my $target = shift;
489
490	return if ($module =~ /^\./);
491	return unless (-f "$subdir/$module/Makefile");
492	return
493	  if ($insttype eq "client" && !grep { $_ eq $module } @client_contribs);
494
495	my $mf = read_file("$subdir/$module/Makefile");
496	$mf =~ s{\\\r?\n}{}g;
497
498	# Note: we currently don't support setting MODULEDIR in the makefile
499	my $moduledir = 'contrib';
500
501	my $flist = '';
502	if ($mf =~ /^EXTENSION\s*=\s*(.*)$/m) { $flist .= $1 }
503	if ($flist ne '')
504	{
505		$moduledir = 'extension';
506		$flist = ParseAndCleanRule($flist, $mf);
507
508		foreach my $f (split /\s+/, $flist)
509		{
510			lcopy("$subdir/$module/$f.control",
511				"$target/share/extension/$f.control")
512			  || croak("Could not copy file $f.control in contrib $module");
513			print '.';
514		}
515	}
516
517	$flist = '';
518	if ($mf =~ /^DATA_built\s*=\s*(.*)$/m) { $flist .= $1 }
519	if ($mf =~ /^DATA\s*=\s*(.*)$/m)       { $flist .= " $1" }
520	$flist =~ s/^\s*//;    # Remove leading spaces if we had only DATA_built
521
522	if ($flist ne '')
523	{
524		$flist = ParseAndCleanRule($flist, $mf);
525
526		foreach my $f (split /\s+/, $flist)
527		{
528			lcopy("$subdir/$module/$f",
529				"$target/share/$moduledir/" . basename($f))
530			  || croak("Could not copy file $f in contrib $module");
531			print '.';
532		}
533	}
534
535	$flist = '';
536	if ($mf =~ /^DATA_TSEARCH\s*=\s*(.*)$/m) { $flist .= $1 }
537	if ($flist ne '')
538	{
539		$flist = ParseAndCleanRule($flist, $mf);
540
541		foreach my $f (split /\s+/, $flist)
542		{
543			lcopy("$subdir/$module/$f",
544				"$target/share/tsearch_data/" . basename($f))
545			  || croak("Could not copy file $f in $subdir $module");
546			print '.';
547		}
548	}
549
550	$flist = '';
551	if ($mf =~ /^DOCS\s*=\s*(.*)$/mg) { $flist .= $1 }
552	if ($flist ne '')
553	{
554		$flist = ParseAndCleanRule($flist, $mf);
555
556		# Special case for contrib/spi
557		$flist =
558"autoinc.example insert_username.example moddatetime.example refint.example timetravel.example"
559		  if ($module eq 'spi');
560		foreach my $f (split /\s+/, $flist)
561		{
562			lcopy("$subdir/$module/$f", "$target/doc/$moduledir/$f")
563			  || croak("Could not copy file $f in contrib $module");
564			print '.';
565		}
566	}
567}
568
569sub ParseAndCleanRule
570{
571	my $flist = shift;
572	my $mf    = shift;
573
574	# Strip out $(addsuffix) rules
575	if (index($flist, '$(addsuffix ') >= 0)
576	{
577		my $pcount = 0;
578		my $i;
579		for (
580			$i = index($flist, '$(addsuffix ') + 12;
581			$i < length($flist);
582			$i++)
583		{
584			$pcount++ if (substr($flist, $i, 1) eq '(');
585			$pcount-- if (substr($flist, $i, 1) eq ')');
586			last      if ($pcount < 0);
587		}
588		$flist =
589		    substr($flist, 0, index($flist, '$(addsuffix '))
590		  . substr($flist, $i + 1);
591	}
592	return $flist;
593}
594
595sub CopyIncludeFiles
596{
597	my $target = shift;
598
599	EnsureDirectories($target, 'include', 'include/libpq', 'include/internal',
600		'include/internal/libpq', 'include/server', 'include/server/parser');
601
602	CopyFiles(
603		'Public headers', $target . '/include/',
604		'src/include/',   'postgres_ext.h',
605		'pg_config.h',    'pg_config_ext.h',
606		'pg_config_os.h', 'dynloader.h',
607		'pg_config_manual.h');
608	lcopy('src/include/libpq/libpq-fs.h', $target . '/include/libpq/')
609	  || croak 'Could not copy libpq-fs.h';
610
611	CopyFiles(
612		'Libpq headers',
613		$target . '/include/',
614		'src/interfaces/libpq/', 'libpq-fe.h', 'libpq-events.h');
615	CopyFiles(
616		'Libpq internal headers',
617		$target . '/include/internal/',
618		'src/interfaces/libpq/', 'libpq-int.h', 'pqexpbuffer.h');
619
620	CopyFiles(
621		'Internal headers',
622		$target . '/include/internal/',
623		'src/include/', 'c.h', 'port.h', 'postgres_fe.h');
624	lcopy('src/include/libpq/pqcomm.h', $target . '/include/internal/libpq/')
625	  || croak 'Could not copy pqcomm.h';
626
627	CopyFiles(
628		'Server headers',
629		$target . '/include/server/',
630		'src/include/', 'pg_config.h', 'pg_config_ext.h', 'pg_config_os.h',
631		'dynloader.h');
632	CopyFiles(
633		'Grammar header',
634		$target . '/include/server/parser/',
635		'src/backend/parser/', 'gram.h');
636	CopySetOfFiles(
637		'',
638		[ glob("src\\include\\*.h") ],
639		$target . '/include/server/');
640	my $D;
641	opendir($D, 'src/include') || croak "Could not opendir on src/include!\n";
642
643	CopyFiles(
644		'PL/pgSQL header',
645		$target . '/include/server/',
646		'src/pl/plpgsql/src/', 'plpgsql.h');
647
648	# some xcopy progs don't like mixed slash style paths
649	(my $ctarget = $target) =~ s!/!\\!g;
650	while (my $d = readdir($D))
651	{
652		next if ($d =~ /^\./);
653		next if ($d eq '.git');
654		next if ($d eq 'CVS');
655		next unless (-d "src/include/$d");
656
657		EnsureDirectories("$target/include/server/$d");
658		my @args = (
659			'xcopy', '/s', '/i', '/q', '/r', '/y', "src\\include\\$d\\*.h",
660			"$ctarget\\include\\server\\$d\\");
661		system(@args) && croak("Failed to copy include directory $d\n");
662	}
663	closedir($D);
664
665	my $mf = read_file('src/interfaces/ecpg/include/Makefile');
666	$mf =~ s{\\\r?\n}{}g;
667	$mf =~ /^ecpg_headers\s*=\s*(.*)$/m
668	  || croak "Could not find ecpg_headers line\n";
669	CopyFiles(
670		'ECPG headers',
671		$target . '/include/',
672		'src/interfaces/ecpg/include/',
673		'ecpg_config.h', split /\s+/, $1);
674	$mf =~ /^informix_headers\s*=\s*(.*)$/m
675	  || croak "Could not find informix_headers line\n";
676	EnsureDirectories($target . '/include', 'informix', 'informix/esql');
677	CopyFiles(
678		'ECPG informix headers',
679		$target . '/include/informix/esql/',
680		'src/interfaces/ecpg/include/',
681		split /\s+/, $1);
682}
683
684sub GenerateNLSFiles
685{
686	my $target   = shift;
687	my $nlspath  = shift;
688	my $majorver = shift;
689
690	print "Installing NLS files...";
691	EnsureDirectories($target, "share/locale");
692	my @flist;
693	File::Find::find(
694		{   wanted => sub {
695				/^nls\.mk\z/s
696				  && !push(@flist, $File::Find::name);
697			  }
698		},
699		"src");
700	foreach (@flist)
701	{
702		my $prgm = DetermineCatalogName($_);
703		s/nls.mk/po/;
704		my $dir = $_;
705		next unless ($dir =~ /([^\/]+)\/po$/);
706		foreach (glob("$dir/*.po"))
707		{
708			my $lang;
709			next unless /([^\/]+)\.po/;
710			$lang = $1;
711
712			EnsureDirectories($target, "share/locale/$lang",
713				"share/locale/$lang/LC_MESSAGES");
714			my @args = (
715				"$nlspath\\bin\\msgfmt",
716				'-o',
717"$target\\share\\locale\\$lang\\LC_MESSAGES\\$prgm-$majorver.mo",
718				$_);
719			system(@args) && croak("Could not run msgfmt on $dir\\$_");
720			print ".";
721		}
722	}
723	print "\n";
724}
725
726sub DetermineMajorVersion
727{
728	my $f = read_file('src/include/pg_config.h')
729	  || croak 'Could not open pg_config.h';
730	$f =~ /^#define\s+PG_MAJORVERSION\s+"([^"]+)"/m
731	  || croak 'Could not determine major version';
732	return $1;
733}
734
735sub DetermineCatalogName
736{
737	my $filename = shift;
738
739	my $f = read_file($filename) || croak "Could not open $filename";
740	$f =~ /CATALOG_NAME\s*\:?=\s*(\S+)/m
741	  || croak "Could not determine catalog name in $filename";
742	return $1;
743}
744
745sub read_file
746{
747	my $filename = shift;
748	my $F;
749	my $t = $/;
750
751	undef $/;
752	open($F, $filename) || die "Could not open file $filename\n";
753	my $txt = <$F>;
754	close($F);
755	$/ = $t;
756
757	return $txt;
758}
759
7601;
761