]> granicus.if.org Git - postgresql/blob - src/tools/msvc/Project.pm
15732b146b8d5a257274f1420f0b16f61177c5c4
[postgresql] / src / tools / msvc / Project.pm
1 package Project;
2
3 #
4 # Package that encapsulates a Visual C++ project file generation
5 #
6 # $PostgreSQL: pgsql/src/tools/msvc/Project.pm,v 1.21 2009/11/12 00:13:00 tgl Exp $
7 #
8 use Carp;
9 use strict;
10 use warnings;
11 use File::Basename;
12
13 sub new
14 {
15     my ($junk, $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            => Win32::GuidGen(),
26         files           => {},
27         references      => [],
28         libraries       => [],
29         suffixlib       => [],
30         includes        => '',
31         prefixincludes  => '',
32         defines         => ';',
33         solution        => $solution,
34         disablewarnings => '4018;4244;4273;4102;4090',
35         disablelinkerwarnings => ''
36     };
37
38     bless $self;
39     return $self;
40 }
41
42 sub AddFile
43 {
44     my ($self, $filename) = @_;
45
46     $self->{files}->{$filename} = 1;
47 }
48
49 sub AddFiles
50 {
51     my $self = shift;
52     my $dir = shift;
53
54     while (my $f = shift)
55     {
56         $self->{files}->{$dir . "\\" . $f} = 1;
57     }
58 }
59
60 sub ReplaceFile
61 {
62     my ($self, $filename, $newname) = @_;
63     my $re = "\\\\$filename\$";
64
65     foreach my $file ( keys %{ $self->{files} } )
66     {
67
68         # Match complete filename
69         if ($filename =~ /\\/)
70         {
71             if ($file eq $filename)
72             {
73                 delete $self->{files}{$file};
74                 $self->{files}{$newname} = 1;
75                 return;
76             }
77         }
78         elsif ($file =~ m/($re)/)
79         {
80             delete $self->{files}{$file};
81             $self->{files}{"$newname\\$filename"} = 1;
82             return;
83         }
84     }
85     confess("Could not find file $filename to replace\n");
86 }
87
88 sub RemoveFile
89 {
90     my ($self, $filename) = @_;
91     my $orig = scalar keys %{ $self->{files} };
92     delete $self->{files}->{$filename};
93     if ($orig > scalar keys %{$self->{files}} )
94     {
95         return;
96     }
97     confess("Could not find file $filename to remove\n");
98 }
99
100 sub RelocateFiles
101 {
102     my ($self, $targetdir, $proc) = @_;
103     foreach my $f (keys %{$self->{files}}) {
104         my $r = &$proc($f);
105         if ($r) {
106            $self->RemoveFile($f);
107            $self->AddFile($targetdir . '\\' . basename($f));
108         }
109     }
110 }
111
112 sub AddReference
113 {
114     my $self = shift;
115
116     while (my $ref = shift)
117     {
118         push @{$self->{references}},$ref;
119         $self->AddLibrary("__CFGNAME__\\" . $ref->{name} . "\\" . $ref->{name} . ".lib");
120     }
121 }
122
123 sub AddLibrary
124 {
125     my ($self, $lib, $dbgsuffix) = @_;
126     
127     if ($lib =~ m/\s/)
128     {
129         $lib = '"' . $lib . """;
130     }
131
132     push @{$self->{libraries}}, $lib;
133     if ($dbgsuffix)
134     {
135         push @{$self->{suffixlib}}, $lib;
136     }
137 }
138
139 sub AddIncludeDir
140 {
141     my ($self, $inc) = @_;
142
143     if ($self->{includes} ne '')
144     {
145         $self->{includes} .= ';';
146     }
147     $self->{includes} .= $inc;
148 }
149
150 sub AddPrefixInclude
151 {
152     my ($self, $inc) = @_;
153
154     $self->{prefixincludes} = $inc . ';' . $self->{prefixincludes};
155 }
156
157 sub AddDefine
158 {
159     my ($self, $def) = @_;
160
161     $def =~ s/"/""/g;
162     $self->{defines} .= $def . ';';
163 }
164
165 sub FullExportDLL
166 {
167     my ($self, $libname) = @_;
168
169     $self->{builddef} = 1;
170     $self->{def} = ".\\__CFGNAME__\\$self->{name}\\$self->{name}.def";
171     $self->{implib} = "__CFGNAME__\\$self->{name}\\$libname";
172 }
173
174 sub UseDef
175 {
176     my ($self, $def) = @_;
177
178     $self->{def} = $def;
179 }
180
181 sub AddDir
182 {
183     my ($self, $reldir) = @_;
184     my $MF;
185
186     my $t = $/;
187     undef $/;
188     open($MF,"$reldir\\Makefile")
189       || open($MF,"$reldir\\GNUMakefile")
190       || croak "Could not open $reldir\\Makefile\n";
191     my $mf = <$MF>;
192     close($MF);
193
194     $mf =~ s{\\\s*[\r\n]+}{}mg;
195     if ($mf =~ m{^(?:SUB)?DIRS[^=]*=\s*(.*)$}mg)
196     {
197         foreach my $subdir (split /\s+/,$1)
198         {
199             next
200               if $subdir eq "\$(top_builddir)/src/timezone"; #special case for non-standard include
201             next
202               if $reldir . "\\" . $subdir eq "src\\backend\\port\\darwin";
203
204             $self->AddDir($reldir . "\\" . $subdir);
205         }
206     }
207     while ($mf =~ m{^(?:EXTRA_)?OBJS[^=]*=\s*(.*)$}m)
208     {
209         my $s = $1;
210         my $filter_re = qr{\$\(filter ([^,]+),\s+\$\(([^\)]+)\)\)};
211         while ($s =~ /$filter_re/)
212         {
213
214             # Process $(filter a b c, $(VAR)) expressions
215             my $list = $1;
216             my $filter = $2;
217             $list =~ s/\.o/\.c/g;
218             my @pieces = split /\s+/, $list;
219             my $matches = "";
220             foreach my $p (@pieces)
221             {
222
223                 if ($filter eq "LIBOBJS")
224                 {
225                     if (grep(/$p/, @main::pgportfiles) == 1)
226                     {
227                         $p =~ s/\.c/\.o/;
228                         $matches .= $p . " ";
229                     }
230                 }
231                 else
232                 {
233                     confess "Unknown filter $filter\n";
234                 }
235             }
236             $s =~ s/$filter_re/$matches/;
237         }
238         foreach my $f (split /\s+/,$s)
239         {
240             next if $f =~ /^\s*$/;
241             next if $f eq "\\";
242             next if $f =~ /\/SUBSYS.o$/;
243             $f =~ s/,$//; # Remove trailing comma that can show up from filter stuff
244             next unless $f =~ /.*\.o$/;
245             $f =~ s/\.o$/\.c/;
246             if ($f =~ /^\$\(top_builddir\)\/(.*)/)
247             {
248                 $f = $1;
249                 $f =~ s/\//\\/g;
250                 $self->{files}->{$f} = 1;
251             }
252             else
253             {
254                 $f =~ s/\//\\/g;
255                 $self->{files}->{"$reldir\\$f"} = 1;
256             }
257         }
258         $mf =~ s{OBJS[^=]*=\s*(.*)$}{}m;
259     }
260
261     # Match rules that pull in source files from different directories
262     # example: pg_crc.c: $(top_srcdir)/src/backend/utils/hash/pg_crc.c
263     my $replace_re = qr{^([^:\n\$]+\.c)\s*:\s*(?:%\s*: )?\$(\([^\)]+\))\/(.*)\/[^\/]+$}m;
264     while ($mf =~ m{$replace_re}m)
265     {
266         my $match = $1;
267         my $top = $2;
268         my $target = $3;
269         $target =~ s{/}{\\}g;
270         my @pieces = split /\s+/,$match;
271         foreach my $fn (@pieces)
272         {
273             if ($top eq "(top_srcdir)")
274             {
275                 eval { $self->ReplaceFile($fn, $target) };
276             }
277             elsif ($top eq "(backend_src)")
278             {
279                 eval { $self->ReplaceFile($fn, "src\\backend\\$target") };
280             }
281             else
282             {
283                 confess "Bad replacement top: $top, on line $_\n";
284             }
285         }
286         $mf =~ s{$replace_re}{}m;
287     }
288
289     # See if this Makefile contains a description, and should have a RC file
290     if ($mf =~ /^PGFILEDESC\s*=\s*\"([^\"]+)\"/m)
291     {
292         my $desc = $1;
293         my $ico;
294         if ($mf =~ /^PGAPPICON\s*=\s*(.*)$/m) { $ico = $1; }
295         $self->AddResourceFile($reldir,$desc,$ico);
296     }
297     $/ = $t;
298 }
299
300 sub AddResourceFile
301 {
302     my ($self, $dir, $desc, $ico) = @_;
303
304     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
305     my $d = ($year - 100) . "$yday";
306
307     if (Solution::IsNewer("$dir\\win32ver.rc",'src\port\win32ver.rc'))
308     {
309         print "Generating win32ver.rc for $dir\n";
310         open(I,'src\port\win32ver.rc') || confess "Could not open win32ver.rc";
311         open(O,">$dir\\win32ver.rc") || confess "Could not write win32ver.rc";
312         my $icostr = $ico?"IDI_ICON ICON \"src/port/$ico.ico\"":"";
313         while (<I>)
314         {
315             s/FILEDESC/"$desc"/gm;
316             s/_ICO_/$icostr/gm;
317             s/(VERSION.*),0/$1,$d/;
318             if ($self->{type} eq "dll")
319             {
320                 s/VFT_APP/VFT_DLL/gm;
321             }
322             print O;
323         }
324     }
325     close(O);
326     close(I);
327     $self->AddFile("$dir\\win32ver.rc");
328 }
329
330 sub DisableLinkerWarnings
331 {
332     my ($self, $warnings) = @_;
333
334     $self->{disablelinkerwarnings} .= ';' unless ($self->{disablelinkerwarnings} eq '');
335     $self->{disablelinkerwarnings} .= $warnings;
336 }
337
338 sub Save
339 {
340     my ($self) = @_;
341
342     # If doing DLL and haven't specified a DEF file, do a full export of all symbols
343     # in the project.
344     if ($self->{type} eq "dll" && !$self->{def})
345     {
346         $self->FullExportDLL($self->{name} . ".lib");
347     }
348
349     # Dump the project
350     open(F, ">$self->{name}.vcproj") || croak("Could not write to $self->{name}.vcproj\n");
351     $self->WriteHeader(*F);
352     $self->WriteReferences(*F);
353     print F <<EOF;
354  <Files>
355 EOF
356     my @dirstack = ();
357     my %uniquefiles;
358     foreach my $f (sort keys %{ $self->{files} })
359     {
360         confess "Bad format filename '$f'\n" unless ($f =~ /^(.*)\\([^\\]+)\.[r]?[cyl]$/);
361         my $dir = $1;
362         my $file = $2;
363
364         # Walk backwards down the directory stack and close any dirs we're done with
365         while ($#dirstack >= 0)
366         {
367             if (join('\\',@dirstack) eq substr($dir, 0, length(join('\\',@dirstack))))
368             {
369                 last if (length($dir) == length(join('\\',@dirstack)));
370                 last if (substr($dir, length(join('\\',@dirstack)),1) eq '\\');
371             }
372             print F ' ' x $#dirstack . "  </Filter>\n";
373             pop @dirstack;
374         }
375
376         # Now walk forwards and create whatever directories are needed
377         while (join('\\',@dirstack) ne $dir)
378         {
379             my $left = substr($dir, length(join('\\',@dirstack)));
380             $left =~ s/^\\//;
381             my @pieces = split /\\/, $left;
382             push @dirstack, $pieces[0];
383             print F ' ' x $#dirstack . "  <Filter Name=\"$pieces[0]\" Filter=\"\">\n";
384         }
385
386         print F ' ' x $#dirstack . "   <File RelativePath=\"$f\"";
387         if ($f =~ /\.y$/)
388         {
389             my $of = $f;
390             $of =~ s/\.y$/.c/;
391             $of =~ s{^src\\pl\\plpgsql\\src\\gram.c$}{src\\pl\\plpgsql\\src\\pl_gram.c};
392             print F '>'
393               . GenerateCustomTool('Running bison on ' . $f,
394                 'cmd /V:ON /c src\tools\msvc\pgbison.bat ' . $f, $of)
395               . '</File>' . "\n";
396         }
397         elsif ($f =~ /\.l$/)
398         {
399             my $of = $f;
400             $of =~ s/\.l$/.c/;
401             print F '>'
402               . GenerateCustomTool('Running flex on ' . $f, 'src\tools\msvc\pgflex.bat ' . $f,$of)
403               . '</File>' . "\n";
404         }
405         elsif (defined($uniquefiles{$file}))
406         {
407
408             # File already exists, so fake a new name
409             my $obj = $dir;
410             $obj =~ s/\\/_/g;
411             print F
412 "><FileConfiguration Name=\"Debug|Win32\"><Tool Name=\"VCCLCompilerTool\" ObjectFile=\".\\debug\\$self->{name}\\$obj"
413               . "_$file.obj\" /></FileConfiguration><FileConfiguration Name=\"Release|Win32\"><Tool Name=\"VCCLCompilerTool\" ObjectFile=\".\\release\\$self->{name}\\$obj"
414               . "_$file.obj\" /></FileConfiguration></File>\n";
415         }
416         else
417         {
418             $uniquefiles{$file} = 1;
419             print F " />\n";
420         }
421     }
422     while ($#dirstack >= 0)
423     {
424         print F ' ' x $#dirstack . "  </Filter>\n";
425         pop @dirstack;
426     }
427     $self->Footer(*F);
428     close(F);
429 }
430
431 sub GenerateCustomTool
432 {
433     my ($desc, $tool, $output, $cfg) = @_;
434     if (!defined($cfg))
435     {
436         return GenerateCustomTool($desc, $tool, $output, 'Debug')
437           .GenerateCustomTool($desc, $tool, $output, 'Release');
438     }
439     return
440 "<FileConfiguration Name=\"$cfg|Win32\"><Tool Name=\"VCCustomBuildTool\" Description=\"$desc\" CommandLine=\"$tool\" AdditionalDependencies=\"\" Outputs=\"$output\" /></FileConfiguration>";
441 }
442
443 sub WriteReferences
444 {
445     my ($self, $f) = @_;
446     print $f " <References>\n";
447     foreach my $ref (@{$self->{references}})
448     {
449         print $f
450 "  <ProjectReference ReferencedProjectIdentifier=\"$ref->{guid}\" Name=\"$ref->{name}\" />\n";
451     }
452     print $f " </References>\n";
453 }
454
455 sub WriteHeader
456 {
457     my ($self, $f) = @_;
458
459     print $f <<EOF;
460 <?xml version="1.0" encoding="Windows-1252"?>
461 <VisualStudioProject ProjectType="Visual C++" Version="8.00" Name="$self->{name}" ProjectGUID="$self->{guid}">
462  <Platforms><Platform Name="Win32"/></Platforms>
463  <Configurations>
464 EOF
465     $self->WriteConfiguration($f, 'Debug',
466         { defs=>'_DEBUG;DEBUG=1;', wholeopt=>0, opt=>0, strpool=>'false', runtime=>3 });
467     $self->WriteConfiguration($f, 'Release',
468         { defs=>'', wholeopt=>0, opt=>3, strpool=>'true', runtime=>2 });
469     print $f <<EOF;
470  </Configurations>
471 EOF
472 }
473
474 sub WriteConfiguration
475 {
476     my ($self, $f, $cfgname, $p) = @_;
477     my $cfgtype = ($self->{type} eq "exe")?1:($self->{type} eq "dll"?2:4);
478     my $libcfg = (uc $cfgname eq "RELEASE")?"MD":"MDd";
479     my $libs = '';
480     foreach my $lib (@{$self->{libraries}})
481     {
482         my $xlib = $lib;
483         foreach my $slib (@{$self->{suffixlib}})
484         {
485             if ($slib eq $lib)
486             {
487                 $xlib =~ s/\.lib$/$libcfg.lib/;
488                 last;
489             }
490         }
491         $libs .= $xlib . " ";
492     }
493     $libs =~ s/ $//;
494     $libs =~ s/__CFGNAME__/$cfgname/g;
495     print $f <<EOF;
496   <Configuration Name="$cfgname|Win32" OutputDirectory=".\\$cfgname\\$self->{name}" IntermediateDirectory=".\\$cfgname\\$self->{name}"
497         ConfigurationType="$cfgtype" UseOfMFC="0" ATLMinimizesCRunTimeLibraryUsage="FALSE" CharacterSet="2" WholeProgramOptimization="$p->{wholeopt}">
498         <Tool Name="VCCLCompilerTool" Optimization="$p->{opt}"
499                 AdditionalIncludeDirectories="$self->{prefixincludes}src/include;src/include/port/win32;src/include/port/win32_msvc;$self->{includes}"
500                 PreprocessorDefinitions="WIN32;_WINDOWS;__WINDOWS__;__WIN32__;EXEC_BACKEND;WIN32_STACK_RLIMIT=4194304;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE$self->{defines}$p->{defs}"
501                 StringPooling="$p->{strpool}"
502                 RuntimeLibrary="$p->{runtime}" DisableSpecificWarnings="$self->{disablewarnings}"
503                 AdditionalOptions="/MP"
504 EOF
505     print $f <<EOF;
506                 AssemblerOutput="0" AssemblerListingLocation=".\\$cfgname\\$self->{name}\\" ObjectFile=".\\$cfgname\\$self->{name}\\"
507                 ProgramDataBaseFileName=".\\$cfgname\\$self->{name}\\" BrowseInformation="0"
508                 WarningLevel="3" SuppressStartupBanner="TRUE" DebugInformationFormat="3" CompileAs="0"/>
509         <Tool Name="VCLinkerTool" OutputFile=".\\$cfgname\\$self->{name}\\$self->{name}.$self->{type}"
510                 AdditionalDependencies="$libs"
511                 LinkIncremental="0" SuppressStartupBanner="TRUE" AdditionalLibraryDirectories="" IgnoreDefaultLibraryNames="libc"
512                 StackReserveSize="4194304" DisableSpecificWarnings="$self->{disablewarnings}"
513                 GenerateDebugInformation="TRUE" ProgramDatabaseFile=".\\$cfgname\\$self->{name}\\$self->{name}.pdb"
514                 GenerateMapFile="FALSE" MapFileName=".\\$cfgname\\$self->{name}\\$self->{name}.map"
515                 SubSystem="1" TargetMachine="1"
516 EOF
517     if ($self->{disablelinkerwarnings})
518     {
519         print $f "\t\tAdditionalOptions=\"/ignore:$self->{disablelinkerwarnings}\"\n";
520     }
521     if ($self->{implib})
522     {
523         my $l = $self->{implib};
524         $l =~ s/__CFGNAME__/$cfgname/g;
525         print $f "\t\tImportLibrary=\"$l\"\n";
526     }
527     if ($self->{def})
528     {
529         my $d = $self->{def};
530         $d =~ s/__CFGNAME__/$cfgname/g;
531         print $f "\t\tModuleDefinitionFile=\"$d\"\n";
532     }
533
534     print $f "\t/>\n";
535     print $f
536 "\t<Tool Name=\"VCLibrarianTool\" OutputFile=\".\\$cfgname\\$self->{name}\\$self->{name}.lib\" IgnoreDefaultLibraryNames=\"libc\" />\n";
537     print $f
538       "\t<Tool Name=\"VCResourceCompilerTool\" AdditionalIncludeDirectories=\"src\\include\" />\n";
539     if ($self->{builddef})
540     {
541         print $f
542 "\t<Tool Name=\"VCPreLinkEventTool\" Description=\"Generate DEF file\" CommandLine=\"perl src\\tools\\msvc\\gendef.pl $cfgname\\$self->{name}\" />\n";
543     }
544     print $f <<EOF;
545   </Configuration>
546 EOF
547 }
548
549 sub Footer
550 {
551     my ($self, $f) = @_;
552
553     print $f <<EOF;
554  </Files>
555  <Globals/>
556 </VisualStudioProject>
557 EOF
558 }
559
560 # Utility function that loads a complete file
561 sub read_file
562 {
563     my $filename = shift;
564     my $F;
565     my $t = $/;
566
567     undef $/;
568     open($F, $filename) || croak "Could not open file $filename\n";
569     my $txt = <$F>;
570     close($F);
571     $/ = $t;
572
573     return $txt;
574 }
575
576 1;