1package Project;
2
3#
4# Package that encapsulates a Visual C++ project file generation
5#
6# src/tools/msvc/Project.pm
7#
8use Carp;
9use strict;
10use warnings;
11use File::Basename;
12
13sub _new
14{
15	my ($classname, $name, $type, $solution) = @_;
16	my $good_types = {
17		lib => 1,
18		exe => 1,
19		dll => 1, };
20	confess("Bad project type: $type\n") unless exists $good_types->{$type};
21	my $self = {
22		name                  => $name,
23		type                  => $type,
24		guid                  => Win32::GuidGen(),
25		files                 => {},
26		references            => [],
27		libraries             => [],
28		suffixlib             => [],
29		includes              => '',
30		prefixincludes        => '',
31		defines               => ';',
32		solution              => $solution,
33		disablewarnings       => '4018;4244;4273;4102;4090;4267',
34		disablelinkerwarnings => '',
35		platform              => $solution->{platform}, };
36
37	bless($self, $classname);
38	return $self;
39}
40
41sub AddFile
42{
43	my ($self, $filename) = @_;
44
45	$self->{files}->{$filename} = 1;
46}
47
48sub AddFiles
49{
50	my $self = shift;
51	my $dir  = shift;
52
53	while (my $f = shift)
54	{
55		$self->{files}->{ $dir . "/" . $f } = 1;
56	}
57}
58
59sub ReplaceFile
60{
61	my ($self, $filename, $newname) = @_;
62	my $re = "\\/$filename\$";
63
64	foreach my $file (keys %{ $self->{files} })
65	{
66
67		# Match complete filename
68		if ($filename =~ m!/!)
69		{
70			if ($file eq $filename)
71			{
72				delete $self->{files}{$file};
73				$self->{files}{$newname} = 1;
74				return;
75			}
76		}
77		elsif ($file =~ m/($re)/)
78		{
79			delete $self->{files}{$file};
80			$self->{files}{"$newname/$filename"} = 1;
81			return;
82		}
83	}
84	confess("Could not find file $filename to replace\n");
85}
86
87sub RemoveFile
88{
89	my ($self, $filename) = @_;
90	my $orig = scalar keys %{ $self->{files} };
91	delete $self->{files}->{$filename};
92	if ($orig > scalar keys %{ $self->{files} })
93	{
94		return;
95	}
96	confess("Could not find file $filename to remove\n");
97}
98
99sub RelocateFiles
100{
101	my ($self, $targetdir, $proc) = @_;
102	foreach my $f (keys %{ $self->{files} })
103	{
104		my $r = &$proc($f);
105		if ($r)
106		{
107			$self->RemoveFile($f);
108			$self->AddFile($targetdir . '/' . basename($f));
109		}
110	}
111}
112
113sub AddReference
114{
115	my $self = shift;
116
117	while (my $ref = shift)
118	{
119		push @{ $self->{references} }, $ref;
120		$self->AddLibrary(
121			"__CFGNAME__/" . $ref->{name} . "/" . $ref->{name} . ".lib");
122	}
123}
124
125sub AddLibrary
126{
127	my ($self, $lib, $dbgsuffix) = @_;
128
129	# quote lib name if it has spaces and isn't already quoted
130	if ($lib =~ m/\s/ && $lib !~ m/^[&]quot;/)
131	{
132		$lib = '"' . $lib . """;
133	}
134
135	push @{ $self->{libraries} }, $lib;
136	if ($dbgsuffix)
137	{
138		push @{ $self->{suffixlib} }, $lib;
139	}
140}
141
142sub AddIncludeDir
143{
144	my ($self, $inc) = @_;
145
146	if ($self->{includes} ne '')
147	{
148		$self->{includes} .= ';';
149	}
150	$self->{includes} .= $inc;
151}
152
153sub AddPrefixInclude
154{
155	my ($self, $inc) = @_;
156
157	$self->{prefixincludes} = $inc . ';' . $self->{prefixincludes};
158}
159
160sub AddDefine
161{
162	my ($self, $def) = @_;
163
164	$def =~ s/"/""/g;
165	$self->{defines} .= $def . ';';
166}
167
168sub FullExportDLL
169{
170	my ($self, $libname) = @_;
171
172	$self->{builddef} = 1;
173	$self->{def}      = "./__CFGNAME__/$self->{name}/$self->{name}.def";
174	$self->{implib}   = "__CFGNAME__/$self->{name}/$libname";
175}
176
177sub UseDef
178{
179	my ($self, $def) = @_;
180
181	$self->{def} = $def;
182}
183
184sub AddDir
185{
186	my ($self, $reldir) = @_;
187	my $mf = read_makefile($reldir);
188
189	$mf =~ s{\\\r?\n}{}g;
190	if ($mf =~ m{^(?:SUB)?DIRS[^=]*=\s*(.*)$}mg)
191	{
192		foreach my $subdir (split /\s+/, $1)
193		{
194			next
195			  if $subdir eq "\$(top_builddir)/src/timezone"
196			;    #special case for non-standard include
197			next
198			  if $reldir . "/" . $subdir eq "src/backend/port/darwin";
199
200			$self->AddDir($reldir . "/" . $subdir);
201		}
202	}
203	while ($mf =~ m{^(?:EXTRA_)?OBJS[^=]*=\s*(.*)$}m)
204	{
205		my $s         = $1;
206		my $filter_re = qr{\$\(filter ([^,]+),\s+\$\(([^\)]+)\)\)};
207		while ($s =~ /$filter_re/)
208		{
209
210			# Process $(filter a b c, $(VAR)) expressions
211			my $list   = $1;
212			my $filter = $2;
213			$list =~ s/\.o/\.c/g;
214			my @pieces = split /\s+/, $list;
215			my $matches = "";
216			foreach my $p (@pieces)
217			{
218
219				if ($filter eq "LIBOBJS")
220				{
221					if (grep(/$p/, @main::pgportfiles, @main::pgcommonfiles)
222						== 1)
223					{
224						$p =~ s/\.c/\.o/;
225						$matches .= $p . " ";
226					}
227				}
228				else
229				{
230					confess "Unknown filter $filter\n";
231				}
232			}
233			$s =~ s/$filter_re/$matches/;
234		}
235		foreach my $f (split /\s+/, $s)
236		{
237			next if $f =~ /^\s*$/;
238			next if $f eq "\\";
239			next if $f =~ /\/SUBSYS.o$/;
240			$f =~ s/,$//
241			  ;    # Remove trailing comma that can show up from filter stuff
242			next unless $f =~ /.*\.o$/;
243			$f =~ s/\.o$/\.c/;
244			if ($f =~ /^\$\(top_builddir\)\/(.*)/)
245			{
246				$f = $1;
247				$self->{files}->{$f} = 1;
248			}
249			else
250			{
251				$self->{files}->{"$reldir/$f"} = 1;
252			}
253		}
254		$mf =~ s{OBJS[^=]*=\s*(.*)$}{}m;
255	}
256
257	# Match rules that pull in source files from different directories, eg
258	# pgstrcasecmp.c rint.c snprintf.c: % : $(top_srcdir)/src/port/%
259	my $replace_re =
260	  qr{^([^:\n\$]+\.c)\s*:\s*(?:%\s*: )?\$(\([^\)]+\))\/(.*)\/[^\/]+\n}m;
261	while ($mf =~ m{$replace_re}m)
262	{
263		my $match  = $1;
264		my $top    = $2;
265		my $target = $3;
266		my @pieces = split /\s+/, $match;
267		foreach my $fn (@pieces)
268		{
269			if ($top eq "(top_srcdir)")
270			{
271				eval { $self->ReplaceFile($fn, $target) };
272			}
273			elsif ($top eq "(backend_src)")
274			{
275				eval { $self->ReplaceFile($fn, "src/backend/$target") };
276			}
277			else
278			{
279				confess "Bad replacement top: $top, on line $_\n";
280			}
281		}
282		$mf =~ s{$replace_re}{}m;
283	}
284
285	$self->AddDirResourceFile($reldir);
286}
287
288# If the directory's Makefile bears a description string, add a resource file.
289sub AddDirResourceFile
290{
291	my ($self, $reldir) = @_;
292	my $mf = read_makefile($reldir);
293
294	if ($mf =~ /^PGFILEDESC\s*=\s*\"([^\"]+)\"/m)
295	{
296		my $desc = $1;
297		my $ico;
298		if ($mf =~ /^PGAPPICON\s*=\s*(.*)$/m) { $ico = $1; }
299		$self->AddResourceFile($reldir, $desc, $ico);
300	}
301}
302
303sub AddResourceFile
304{
305	my ($self, $dir, $desc, $ico) = @_;
306
307	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
308	  localtime(time);
309	my $d = sprintf("%02d%03d", ($year - 100), $yday);
310
311	if (Solution::IsNewer("$dir/win32ver.rc", 'src/port/win32ver.rc'))
312	{
313		print "Generating win32ver.rc for $dir\n";
314		open(my $i, '<', 'src/port/win32ver.rc')
315		  || confess "Could not open win32ver.rc";
316		open(my $o, '>', "$dir/win32ver.rc")
317		  || confess "Could not write win32ver.rc";
318		my $icostr = $ico ? "IDI_ICON ICON \"src/port/$ico.ico\"" : "";
319		while (<$i>)
320		{
321			s/FILEDESC/"$desc"/gm;
322			s/_ICO_/$icostr/gm;
323			s/(VERSION.*),0/$1,$d/;
324			if ($self->{type} eq "dll")
325			{
326				s/VFT_APP/VFT_DLL/gm;
327			}
328			print $o $_;
329		}
330		close($o);
331		close($i);
332	}
333	$self->AddFile("$dir/win32ver.rc");
334}
335
336sub DisableLinkerWarnings
337{
338	my ($self, $warnings) = @_;
339
340	$self->{disablelinkerwarnings} .= ','
341	  unless ($self->{disablelinkerwarnings} eq '');
342	$self->{disablelinkerwarnings} .= $warnings;
343}
344
345sub Save
346{
347	my ($self) = @_;
348
349# If doing DLL and haven't specified a DEF file, do a full export of all symbols
350# in the project.
351	if ($self->{type} eq "dll" && !$self->{def})
352	{
353		$self->FullExportDLL($self->{name} . ".lib");
354	}
355
356# Warning 4197 is about double exporting, disable this per
357# http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99193
358	$self->DisableLinkerWarnings('4197') if ($self->{platform} eq 'x64');
359
360	# Dump the project
361	open(my $f, '>', "$self->{name}$self->{filenameExtension}")
362	  || croak(
363		"Could not write to $self->{name}$self->{filenameExtension}\n");
364	$self->WriteHeader($f);
365	$self->WriteFiles($f);
366	$self->Footer($f);
367	close($f);
368}
369
370sub GetAdditionalLinkerDependencies
371{
372	my ($self, $cfgname, $separator) = @_;
373	my $libcfg = (uc $cfgname eq "RELEASE") ? "MD" : "MDd";
374	my $libs = '';
375	foreach my $lib (@{ $self->{libraries} })
376	{
377		my $xlib = $lib;
378		foreach my $slib (@{ $self->{suffixlib} })
379		{
380			if ($slib eq $lib)
381			{
382				$xlib =~ s/\.lib$/$libcfg.lib/;
383				last;
384			}
385		}
386		$libs .= $xlib . $separator;
387	}
388	$libs =~ s/.$//;
389	$libs =~ s/__CFGNAME__/$cfgname/g;
390	return $libs;
391}
392
393# Utility function that loads a complete file
394sub read_file
395{
396	my $filename = shift;
397	my $F;
398	my $t = $/;
399
400	undef $/;
401	open($F, '<', $filename) || croak "Could not open file $filename\n";
402	my $txt = <$F>;
403	close($F);
404	$/ = $t;
405
406	return $txt;
407}
408
409sub read_makefile
410{
411	my $reldir = shift;
412	my $F;
413	my $t = $/;
414
415	undef $/;
416	open($F, '<', "$reldir/GNUmakefile")
417	  || open($F, '<', "$reldir/Makefile")
418	  || confess "Could not open $reldir/Makefile\n";
419	my $txt = <$F>;
420	close($F);
421	$/ = $t;
422
423	return $txt;
424}
425
4261;
427