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